Quantcast
Channel: かずきのBlog@hatena
Viewing all 1388 articles
Browse latest View live

「Goプログラミング実践入門 標準ライブラリでゼロからWebアプリを作る」を買ってみた

$
0
0

なんか、一番好きな Windows アプリ作りは苦痛そうな雰囲気を多少感じたので、Webアプリ方面に手を出してみようと思っていたところ最適そうな本があったのでアマゾンでぽちりました!

※:アフィリエイトリンクなので嫌な人は気を付けて!

本当はプログラミング言語Goが欲しかったけどKindle無かったので2番目に欲しい!!って思ったWebアプリ開発に特化した奴を買いました。楽しみ!


Go プログラミング実践入門を読みながら Go での Web App のハローワールドをしようとして脱線した話し

$
0
0

Go には標準で Web アプリを作るための機能が入ってるらしい!? 他の言語でいうところの Web アプリ開発のためのフレームワークというのは、あるんだろうなぁと思って調べてみたら凄く素敵なまとめをみつけました。

github.com

いいね。本読み終わったら次に手を出してみようかなぁ。

Go での Web アプリ

とりあえず net/httpをインポートしてたら簡単なハローワールドは出来上がる。 ドキュメントはここみたい。

http - The Go Programming Language

書籍の最初では、Web アプリを作るための書籍なので HandleFuncでリクエスト受け取るのに使ってますがドキュメントを見ると以下のようになってる。

Package http provides HTTP client and server implementations. 

なるほど、クライアントとサーバー兼ね備えてるんですね。じゃぁ早速クライアント側試してみようと思います。 試すのはこれ。

zipcloud.ibsnet.co.jp

http://zipcloud.ibsnet.co.jp/api/searchに対して zipcode パラメータつけて GET するだけで OK。とても直感的ですね。 早速

package main

import (
    "fmt""io/ioutil""net/http"
)

func main() {
    res, err := http.Get("http://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060")
    if err != nil {
        fmt.Println(err.Error())
        return
    }

    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)

    if err != nil {
        fmt.Println(err.Error())
        return
    }

    fmt.Print(string(body))
}

実行するとこんな感じ。

{
        "message": null,
        "results": [
                {
                        "address1": "高知県",
                        "address2": "南国市",
                        "address3": "蛍が丘",
                        "kana1": "コウチケン",
                        "kana2": "ナンコクシ",
                        "kana3": "ホタルガオカ",
                        "prefcode": "39",
                        "zipcode": "7830060"
                }
        ],
        "status": 200
}

いいね!せっかくなので JSON もパースしておきたいですね。愛用している https://quicktype.ioで上記 JSON を張り付けて Go 言語用の構造体とかを作ります。サクッと出来ていいですね。コピペして完了。

// To parse and unparse this JSON data, add this code to your project and do:////    zipCodeResult, err := UnmarshalZipCodeResult(bytes)//    bytes, err = zipCodeResult.Marshal()package main

import"encoding/json"func UnmarshalZipCodeResult(data []byte) (ZipCodeResult, error) {
    var r ZipCodeResult
    err := json.Unmarshal(data, &r)
    return r, err
}

func (r *ZipCodeResult) Marshal() ([]byte, error) {
    return json.Marshal(r)
}

type ZipCodeResult struct {
    Message interface{} `json:"message"`
    Results []Result    `json:"results"`
    Status  int64`json:"status"`
}

type Result struct {
    Address1 string`json:"address1"`
    Address2 string`json:"address2"`
    Address3 string`json:"address3"`
    Kana1    string`json:"kana1"`
    Kana2    string`json:"kana2"`
    Kana3    string`json:"kana3"`
    Prefcode string`json:"prefcode"`
    Zipcode  string`json:"zipcode"`
}

そして、これを使って main 関数を書いて

package main

import (
    "fmt""io/ioutil""net/http"
)

func main() {
    res, err := http.Get("http://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060")
    if err != nil {
        fmt.Println(err.Error())
        return
    }

    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)

    if err != nil {
        fmt.Println(err.Error())
        return
    }

    r, err := UnmarshalZipCodeResult(body)
    if err != nil {
        fmt.Println(err.Error())
        return
    }

    fmt.Printf("status code: %v, address1: %v, address2: %v, address3: %v\n", r.Status, r.Results[0].Address1, r.Results[0].Address2, r.Results[0].Address3)
}

実行するとこうなりました。

status code: 200, address1: 高知県, address2: 南国市, address3: 蛍が丘

うん。ばっちり。あれ?本読んでたけど違うことしちゃってた。まぁいっか。

Go プログラミング実践入門を読みながら Go での Web App のハローワールド

$
0
0

先ほど脱線して http のクライアント側の機能に走ってしまったので気を取り直してサーバー側に行きたいと思います。

といってもハローワールドするだけなら凄く簡単。本当に凄く

package main

import (
    "fmt""net/http"
)

func main() {
    http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(res, "Hello world")
    })
    http.ListenAndServe(":8080", nil)
}

HandleFunc でハンドラーを登録して ListenAndServe で待つ。以上終了。できた。

f:id:okazuki:20181031222105p:plain

試しに http://localhost:8080/hoge/foo?bar=puyopuyoを叩いてハンドラーの関数にブレークポイントはってリクエストの構造体の中身を見てみた。

f:id:okazuki:20181031222352p:plain

当然と言えば当然だけど基本的なものは詰まってるので、後は頑張ればいけそう。

ちなみにパスに対して何かしらするものを登録するのは HandleFunc の他に Hundle というのがあるみたい。 Handle には以下のような Handler インターフェースを渡す。

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

つまり、さっきのをこれを使う用に書き直すとこんな感じ。

package main

import (
    "fmt""net/http"
)

type MyHandler struct {
}

func (*MyHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(res, "Hello world!! Handler version.")
}

func main() {
    http.Handle("/", &MyHandler{})
    http.ListenAndServe(":8080", nil)
}

今回は MyHandler がデータを何も持ってない構造体なので、何も嬉しくないけど設定情報とかを持たせれる点で HandleFunc より柔軟そう。

現に以下の FileServer 関数の例では http.FileServerを使って特定ディレクトリの下のファイルを返すようにしてるみたい。

https://golang.org/pkg/net/http/#FileServer

試しに以下のコードを動かすと C:\Workの下のファイルを返してくれるようになった。

package main

import (
    "fmt""net/http"
)

type MyHandler struct {
}

func (*MyHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(res, "Hello world!! Handler version.")
}

func main() {
    http.Handle("/", http.FileServer(http.Dir("/work")))
    //http.Handle("/", &MyHandler{})
    http.ListenAndServe(":8080", nil)
}

f:id:okazuki:20181031224404p:plain

http.FileServer関数で作ったやつは、素直にパスをみてファイルを返してくれるみたいなので/static/とかにマッピングすると C:\Workの下にも static フォルダを切らないと NotFound になる。 基本的には、パスから static を外して欲しいから、そこらへんのことをしてくれると思われるのが http.StripPrefix関数。こんな感じでいける。

package main

import (
    "fmt""net/http"
)

type MyHandler struct {
}

func (*MyHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
    fmt.Fprintf(res, "Hello world!! Handler version.")
}

func main() {
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/work"))))
    http.Handle("/", &MyHandler{})
    http.ListenAndServe(":8080", nil)
}

これで http://localhost:8080/static/hello.txtにアクセスすると c:\work\hello.txtの中身が返ってくるし、http://localhost:8080/hogehogeみたいなのにアクセスすると MyHandler のほうに処理がいく。

結構単純でいいね。

Prism の v7.1 がリリースされてました

$
0
0

Prism v7.1 が2週間ちょっと前にリリースされてました。

github.com

かなり大きな更新に見えます。 個人的にインパクトが大きそうだと思ったのが Autofac と MEF が Prism の将来のサポート対象から外れてしまうことでしょうか。 Autofac はイミュータブルになってるので、Prism の提供するモジュールまわりの機能と相性がよくないみたいですね。

ということで、特に理由が無ければ Unity (こっちはこっちで名前空間が昔と変わったりしてる)を使うのがいいと思います。

Xamarin.Forms 向けでは XAML でナビゲーションを定義出来たり、あと一番アツイのが ContainerProviderです。今まで Converter とかに対して DI しようと思ったらコードで DI コンテナからインスタンス取ってきて ResourceDictionary に自分で追加とかしないといけなかったけど、それが XAML でいける。ヤバイ。

あとは、この v7.1 系が WPF 向けの最初の v7 系の Prism のリリースというのも個人的にアツイです。6.3 から 7.1 への更新ということなので結構な破壊的変更のオンパレードです。 v6 系で作ってるひとは、アップデートするときにはちょっと苦痛が伴うかもしれません。

例えば Bootstrapper クラスが非推奨になって PrismApplication を使うようになっていたり、今までは DI コンテナへのインスタンスの登録は生の DI コンテナの API をたたいてたけど抽象化するレイヤーが設けられていたり(多分がんばれば裏で動いてる DI コンテナのインスタンスにダイレクトアクセスして、既存の登録ロジックに回したりは出来ると思う「要出典」)

モジュール系のインターフェースが Prism.Wpf に行ってたり、Unity のバージョンを上げたので、そっちの破壊的変更にも引きずられたり(名前空間が違う)。

とりあえず、新しいものが出てくると楽しいですね。

Bot Builder SDK v4 の OAuthPrompt を使って出したログインボタンが Microsoft Teams で反応しない

$
0
0

しないんですけど!?下の issue の通りみたい。

github.com

まじかぁ…

The sign in card's button has an ActionType of signin and teams does not support this ActionType. In order to make this work as present time you need to go in and change the ActionType to ActionType.OpenUrl There are a few other issues discussing this one is #2104

この issue で提示されてる解決策は v3 ベースっぽいので v4 ではそのまま使えないくさい。 ということで、OAuthPrompt.cs をリポジトリからこぴってきて。

botbuilder-dotnet/OAuthPrompt.cs at master · Microsoft/botbuilder-dotnet · GitHub

SendOAuthCardAsync メソッドで2か所 SignIn の ActionTypes を使ってる箇所があるので OpenUrl に変えます。 そしてオリジナルの OAuthPrompt を使ってる箇所を、自分で作成したオレオレ OAuthPrompt に差し替えれば動きます。

もうちょっと詳しく Bot Builder SDK v4 の処理を追うと、issue でやってるようにメッセージ送信途中で何かしら処理をフックしてアクションを書き換えることで対応できるんじゃないかと思ってますが、とりあえずの方法としてはこれで…。

Google アシスタントのエージェントを Azure Functions + C# で作る

$
0
0

Dialogflow v2 に対応した C# の SDK が実はこっそりあります。 1.0.0-beta2 (2018/1104 現在) なので正式版ではないですが、きっと近いうちに出ると思う!!

以下のパッケージを入れましょう。

www.nuget.org

Visual Studio で追加するときはプレリリースパッケージのチェックを入れてから検索しましょう。

入れたらあとは JSON をパースしたりするだけです。ただ、JSON.NET ではなく Google の提供している JSON のパーサーを使います。Google.Protobuf の名前空間にあります。

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Google.Cloud.Dialogflow.V2;
using Google.Protobuf;
usingstatic Google.Cloud.Dialogflow.V2.Intent.Types.Message.Types;
usingstatic Google.Cloud.Dialogflow.V2.Intent.Types;
using Microsoft.Azure.WebJobs.Hosting;

namespace HogeHoge
{
    publicstaticclass Function1
    {
        [FunctionName("Function1")]
        publicstatic async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)]HttpRequest req,
            ILogger log)
        {
            var parser = new JsonParser(JsonParser.Settings.Default.WithIgnoreUnknownFields(true)); // パーサーを作る
            var webhookRequest = parser.Parse<WebhookRequest>(await req.ReadAsStringAsync()); // パースする// 例えば Webhook がすべてのパラメータがそろった状態で呼び出されたかを確認したりif (!webhookRequest.QueryResult.AllRequiredParamsPresent)
            {
                log.LogInformation("This request does not have all required parameters");
                returnnew OkResult();
            }
            // パラメーターをとって色々したり
            var requestedParameters = webhookRequest.QueryResult.Parameters;

            // 戻りは、WebhookResponse を返せば OK
            var webhookResponse = new WebhookResponse();
            webhookResponse.FulfillmentText = $"応答内容";
            // 結果の JSON も Google.Protobuf を使ってシリアライズreturnnew ProtcolBufJsonResult(webhookResponse, JsonFormatter.Default);
        }
    }

    // Google.Protobuf を使ってシリアライズする IActionResult の実装publicclass ProtcolBufJsonResult : IActionResult
    {
        privatereadonlyobject _obj;
        privatereadonly JsonFormatter _formatter;

        public ProtcolBufJsonResult(object obj, JsonFormatter formatter)
        {
            _obj = obj;
            _formatter = formatter;
        }

        public async Task ExecuteResultAsync(ActionContext context)
        {
            context.HttpContext.Response.Headers.Add("Content-Type", new Microsoft.Extensions.Primitives.StringValues("application/json"));
            var stringWriter = new StringWriter();
            _formatter.WriteValue(stringWriter, _obj);
            await context.HttpContext.Response.WriteAsync(stringWriter.ToString());
        }
    }
}

あとはデプロイして関数のエンドポイントを Dialogflow のフルフィルメントに設定して完了。

早く正式版でないかなぁ。

Azure Functions で Alexa, Google Home, LINE Clova 対応のスキル(アプリ?アシスタント?)を作る方法

$
0
0

スマートスピーカーのスキルは、基本的にスマートスピーカーが受け取った音声をテキスト化する部分、テキストから意図(インテント)とキーワード(スロットとかエンテティとかって言われる)を抜き出すところまで、各スマートスピーカーを提供してくれているベンダーが面倒見てくれます。

そして、そこから解析結果が詰まった JSON を Webhook めがけて投げてくれます。 開発者がゴリゴリコードを書いてカスタマイズできる部分は、この Webhook の先のコードという形が一般的です。

ちなみに、Microsoft の Bot Framework を使った場合は意図やキーワードを抜き出すことはしてくれないです。 その代わり SDK 側に Microsoft の LUIS と呼ばれるテキストから意図やキーワードを抜き出すサービスとの連携機能があるので、自分で好きなように呼び出すことが出来るようになっています。

例えば、その時の会話の状態に応じて LUIS を呼び分けるといったことが出来ます。最初はおおまかに何がしたいかを判別することに特化して学習させた LUIS に解析をお願いして、その先では、それぞれのやりたいことに特化して学習させた LUIS を呼び出すといったことが出来ます。 手間がかかるぶんこったものを実現可能になっている感じですね。

さて、話しを戻します。

Webhook で飛んでくる JSON ですがスマートスピーカーのプラットフォームごとに互換性がありません。ですが、その先でやりたいことはだいたい共通でしょう。

ということで、受け口と必要なデータを抜き出す部分だけ別関数(HTTPのエンドポイントが別になる)に分けておいて、そこから共通ロジックを呼ぶ形で 1 つの Azure Functions で対応可能になります。

f:id:okazuki:20181105105153p:plain

このとき node.js でやるなら公式の SDK が使えるのでは…と思います。express に依存している場合は azure-functions-express というパッケージで結構動きます。例えば Clova の場合は以下のような感じ。

blog.okazuki.jp

C# の場合は、野良 SDK などを駆使することになります。

Clova の場合はこんな感じ。

blog.okazuki.jp

Google Home の場合はこんな感じ。唯一公式 SDK (ただし、まだベータ)

blog.okazuki.jp

Alexa の場合は、ブログには書いてませんが Alxa.NET というパッケージがあります。 以下の記事は Lambda を使ってますが、基本的にリクエストのボディを SkillRequest に JSON.NET を使ってパースして、 SkillResponse を作って new OkObjectResult(response);で返してやる感じです。

www.atmarkit.co.jp

まとめ

ということで、複数スマートスピーカーに対応したい人は昔から GUI アプリケーション開発で言われていた見た目とロジックの分離と同じようにスマートスピーカーに依存する部分とロジックを分離することで、低コストで対応が可能になるのではと思います!

ちなみに

ここでは Azure Functions と言ってますが、何を使っててもプラットフォーム依存の部分とそうでない部分を分離して書くことで今回のと同じように 1 つのアプリで 3 プラットフォーム対応とか出来ます。AWS Lambda でも Heroku 上にデプロイしたものでも、レンタルサーバー上にデプロイしたアプリでも!

それでは、良い VUI ライフを!

Microsoft Tech Summit 2018 で Windows 10 対応のデスクトップアプリを作る技術 というタイトルで発表してきました

$
0
0

2018/11/05 - 2018/11/07 で開催された Microsoft Tech Summit 2018 で登壇してきました。

その時のセッション用に準備したコードを以下のリポジトリに公開しました。

github.com

伝えたかったこと

AI とか無関係じゃないし、新機能とかもあるし、デスクトップアプリケーションがつまらないプラットフォームではないということをお伝え出来ればよかったかなと思います。

肩身狭いけどね!


Azure Functions + Application Insights で POST されたデータのログを残す(C# & JavaScript)

$
0
0

liginc.co.jp

なんとなく見かけたので Azure でもやってみましょう。 Azure なら一番簡単に REST API 作るんだったら Azure Functions かなぁ。ログは Application Insights かなぁ。

Azure Functions のドキュメント - チュートリアル | Microsoft Docs

Azure Application Insights とは何か | Microsoft Docs

全体像

f:id:okazuki:20181113185007p:plain

Azure Functions は Lambda と違って HTTPTrigger というものがあって、Functions だけで REST API 作れます。 本格的に API として管理したければ API Management を使えば OK。

開発

じゃぁやってみましょう。本格的に開発するときはローカルに開発環境を作ってやるのが王道ですが、今回は使い捨てレベルくらいのものなのでポータル上で開発してみようと思います。

適材適所。

ポータルから Function App を作ります。作るときに一緒に Application Insights を作るのを忘れずに。 作り忘れた場合は、Application Insights を別途作って Function App のアプリケーション設定に Application Insights のキーを設定します。

作成画面はこんな感じ。

f:id:okazuki:20181113185648p:plain

そして関数を作っていく。今回は先ほど言ったようにポータル内を選ぶ。

f:id:okazuki:20181113185946p:plain

webhook + API を選ぶ。

f:id:okazuki:20181113190011p:plain

暫くまつと HttpTrigger1 というトリガーが出来るのでコードをさくっと以下のようにします。

#r "Newtonsoft.Json"using System.Net;
using System.IO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

publicstatic async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    log.LogInformation(await new StreamReader(req.Body).ReadToEndAsync());
    returnnew OkObjectResult(new { message = "Powered by Azure" });
}

引数にわたってくる ILogger にログはけば Application Insights に行ってくれる。

実行ボタンを押すとテスト実行が出来ます。実行ボタンを押すと以下のようにログも出るのでちゃんと動いてることがわかります。

f:id:okazuki:20181113190605p:plain

関数の URL の取得で、この関数を叩くための URL がゲットできるので Postman あたりで叩いてみます。

f:id:okazuki:20181113190708p:plain

こんな感じ

f:id:okazuki:20181113190831p:plain

Application Insights のライブメトリクス ストリームを選ぶとリアルタイムでログが流れていくのが見えます。

f:id:okazuki:20181113191214p:plain

数分まつと Application Insights の検索からもひっかかるようになります。

f:id:okazuki:20181113191322p:plain

リクエストで出た一連のログとかも見れるので割と便利。

f:id:okazuki:20181113191439p:plain

JavaScript でもいける

C# になじみのない人は JavaScript でもいけます。

docs.microsoft.com

Function App 作るときにランタイムを dotnet ではなく JavaScript のほうを選んで作ると JavaScript でいけます。C# の時と同じ手順で webhook + API を作るといけます。

index.js の編集画面になるので以下のコードをさくっと書きましょう。 こういうのには個人的には JavaScript お手軽なので好きです。

module.exports = async function (context, req) {
    context.log(req.body);
    context.res = {
        body: { message: "Powered by Azure." }
    };
};

JavaScript のほうは引数にわたってくる context.log で出力した内容が Application Insights に行きます。

Postman などで叩くと同じようにライブメトリクス ストリームや検索でリクエストのボディを確認できるようになります。

f:id:okazuki:20181113192248p:plain

f:id:okazuki:20181113192319p:plain

まとめ

今回は JavaScript 本当に簡単でいい。(今回の例では C# ちょっとめんどい)

触ってみたいと思った人は

最近できた Microsoft Learn を使うと実際の Azure 環境を無料でクレカ不要で学習用だけに使うことが出来るので、そこの Azure Functions あたりのコースを流してみるのをお勧めします。

docs.microsoft.com

Go プログラミング実践入門を読みながら Go での Web App「mux と Cookie」

$
0
0

Go 言語で Web アプリを作るときにもう一つ外したらいけなさそうなものとして Mux というのがあるみたい。 Multiplexer っていうのかな。

package main

import (
    "fmt""net/http"
)

func handleRequest(w http.ResponseWriter, req *http.Request) {
    name, ok := req.URL.Query()["name"]
    if ok {
        w.Write([]byte(fmt.Sprintf("Hello %v.", name[0])))
    } else {
        w.Write([]byte("Hello unknown user"))
    }
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handleRequest)

    http.ListenAndServe("localhost:8080", mux)
}

使うとこんな感じ。今まで http に対して直接やってたことを mux に対してやるイメージ。 世の中の Go 向けの Web フレームワークは mux をベースにしてるものもあるらしい。

Cookie

クッキーを使うには、http.Cookie を使う。http.Request から Cookie メソッドを呼ぶと引数に指定した名前の Cookie のポインタとエラーが返ってくる。クッキーの設定は http.SetCookie 関数を使う。http.ResponseWriter と *Cookie を渡す感じ。

挙動を見る感じ body への出力より前にクッキーは設定しないとダメみたい。

package main

import (
    "fmt""net/http""strconv"
)

func handleRequest(w http.ResponseWriter, req *http.Request) {
    count, err := req.Cookie("_counter")
    if err != nil {
        count = &http.Cookie{Name: "_counter", Value: "0", HttpOnly: true}
    }

    currentCount, err := strconv.ParseInt(count.Value, 0, 64)
    count.Value = strconv.Itoa(int(currentCount + 1))
    http.SetCookie(w, count)

    name, ok := req.URL.Query()["name"]
    if ok {
        w.Write([]byte(fmt.Sprintf("Hello %v. You have been visiting this site %v times.", name[0], count.Value)))
    } else {
        w.Write([]byte("Hello unknown user"))
    }

}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handleRequest)

    http.ListenAndServe("localhost:8080", mux)
}

こうすると何回かアクセスするとカウントアップしていく。

f:id:okazuki:20181122161928p:plain

Go プログラミング実践入門を読みながら Go での Web App「テンプレート」

$
0
0

Go 言語って標準ライブラリにテンプレートまであるのか。便利。

ということで使ってみましょう。

使い方は簡単。template.Must(template.ParseFiles("templateFilePath1", "templateFilePath2", ...) みたいにしてテンプレートをパースする。パースしたら ExecuteTemplate メソッドで出力先とテンプレート名とテンプレートに渡す値を指定して完成。

package main

import (
    "html/template""net/http"
)

type pageData struct {
    Title   string
    Message string
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{Title: "Template sample title", Message: "Template sample message"})
    })

    http.ListenAndServe("localhost:8080", mux)
}

テンプレートは以下のような感じになります。

まずは templates/index.html

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html>
{{ end }}

templates/test.html はこんな感じ。

{{ define "test" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Test</title></head><bodystyle="background-color: yellow"><h1>{{ .Title }}</h1><p>{{ .Message }}</p></body></html>
{{ end }}

基本的には {{ }}でくくった中に何か書く。先頭は define でレイアウト名(ExecuteTemplate で指定する名前)を指定して最後に end でおしまい。

{{ .プロパティ名 }} とかで ExecuteTemplate で渡されたデータにアクセスできる感じっぽい。

動かしてみるとちゃんと動いた。

f:id:okazuki:20181122165325p:plain

今回のプログラムは URL のパラメータでテンプレート名指定するからこんな感じでも動く。

f:id:okazuki:20181122165403p:plain

{{ range .xxx }} でループも行ける。テンプレートをいじって

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul>
            {{ range .Records }}
            <li>{{ .Name }}</li>
            {{ end }}
        </ul></body></html>
{{ end }}

main.go もそれにあわせて変更。

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "Template sample message",
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

いい感じに動く。

f:id:okazuki:20181122171033p:plain

さらに if やカスタムの関数とかも定義出来るみたい。今回は偶数番目のデータと奇数番目のデータで色を変えたかったので、mod という関数を追加したうえでパースして実行してみました。

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "Template sample message",
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

テンプレートはこんな感じになりました。

{{ define "index" }}
<!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title>Hello</title></head><body><h1>{{ .Title }}</h1><p>{{ .Message }}</p><hr/><ul>
            {{ range $index, $record := .Records }}
            {{ $isEvenRow := eq (mod $index 2) 0 }}
            <listyle="color: {{ if $isEvenRow}} red {{ else }} black {{ end }}">{{ $index }}{{ $record.Name }}</li>
            {{ end }}
        </ul></body></html>
{{ end }}

if や range のインデックスの取得方法と関数呼び出しと盛りだくさん。実行するとこんな感じです。

f:id:okazuki:20181122173525p:plain

あとはサニタイズとかもあるみたいですね。普通に Go 側とこういう風に書くと…

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message string
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: "<script type='text/javascript`>alert('Template sample message')</script>", // !?
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

素晴らしい。

f:id:okazuki:20181122173712p:plain

あえてエスケープしたくないときは template.HTML を使う。まぁレアケースだけどマークダウンエディターみたいなものを作りたいときとかは必要。

こんな感じで

package main

import (
    "html/template""net/http"
)

type record struct {
    Name string
}

type pageData struct {
    Title   string
    Message template.HTML
    Records []record
}

func main() {
    templateFiles := []string{"templates/index.html", "templates/test.html"}
    templates := template.Must(template.New("").Funcs(template.FuncMap{
        "mod": func(x int, y int) int {
            return x % y
        },
    }).ParseFiles(templateFiles...))

    mux := http.NewServeMux()
    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        templateName := "index"
        templateNames, ok := req.URL.Query()["template"]
        if ok {
            templateName = templateNames[0]
        }

        templates.ExecuteTemplate(w, templateName, pageData{
            Title:   "Template sample title",
            Message: template.HTML("<script type='text/javascript'>alert('Template sample message')</script>"),
            Records: []record{
                record{Name: "aaaaaaaa"},
                record{Name: "bbbbbbbb"},
                record{Name: "cccccccc"},
                record{Name: "dddddddd"},
                record{Name: "eeeeeeee"},
            },
        })
    })

    http.ListenAndServe("localhost:8080", mux)
}

実行するとこうなる。ばっちり。

f:id:okazuki:20181122174547p:plain

Livet 2.1.0 をリリースしました

$
0
0

Pull Request を送って頂いたので取り込んでリリースしました。

www.nuget.org

それに合わせて Visual Studio 拡張機能も更新しています。

marketplace.visualstudio.com

誰かドキュメント書いてプルリクエストください。

何処でも動くサーバーレスプラットフォーム Azure Functions

$
0
0

この記事は Serverless2 Advent Calendar 2018 の 2 日目の記事です。

qiita.com

本文

最近 Docker でパッケージングしたらどこでも動くが実現してるようなものなので、実質 Docker でパッケージング出来たら run anywhere な感じということです!

そして、Azure Functions は Docker をサポートしています。(2018/12/02 時点ではプレビュー) ということでやってみましょう。私は Windows 10 に Docker を入れて試しました。

基本的には以下のドキュメントに沿ってやります。

docs.microsoft.com

ではやってみましょう。 といっても難しいことは特になくて、func コマンドでプロジェクトを作るときに --dockerオプションをつけてやります。任意のフォルダで以下のコマンドをうってみましょう。

func init --docker

そうすると dotnet, node, python から選択できるので今回は node で行ってみようと思います。実行結果は以下のような感じのログになります。Dockerfile まで出来ちゃってますね!

Select a worker runtime: node
Writing .gitignore
Writing host.json
Writing local.settings.json
Writing C:\Users\kaota\.vscode\extensions.json
Writing Dockerfile

早速 Visual Studio Code で開いてみましょう。

FROM mcr.microsoft.com/azure-functions/node:2.0

ENV AzureWebJobsScriptRoot=/home/site/wwwroot
COPY . /home/site/wwwroot

思ったよりシンプル。確かに node の場合はビルドとかいりませんしね。因みに dotnet を選択すると以下のような Dockerfile が生成されます。

FROM microsoft/dotnet:2.1-sdk AS installer-env

COPY . /src/dotnet-function-app
RUN cd /src/dotnet-function-app && \
    mkdir -p /home/site/wwwroot && \
    dotnet publish *.csproj --output /home/site/wwwroot

FROM mcr.microsoft.com/azure-functions/dotnet:2.0
ENV AzureWebJobsScriptRoot=/home/site/wwwroot

COPY --from=installer-env ["/home/site/wwwroot", "/home/site/wwwroot"]

こちらも十分シンプルに見えます!公式がおぜん立てしてくれたベースイメージがあるのは強いですね。

このままだと空の関数になってしまうので func newをうちこんで HttpTrigger の関数を 1 つ作ります。 関数名は Echo を指定しました。そして生成されたファイルをちょっと変えて以下のようにします。

function.json

{"disabled": false,
  "bindings": [{"authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get"
      ]},
    {"type": "http",
      "direction": "out",
      "name": "res"
    }]}

index.js

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');
    context.res = {
        status: 200,
        body: `You said '${req.query.message}'`
    };
};

まずは、func startでローカルで実行してみます。先ほど作成した Echo 関数がちゃんと認識されてることがわかります。

Http Functions:

        Echo: [GET] http://localhost:7071/api/Echo

curl コマンドで叩いてみるとこんな感じ。ばっちりですね。

> curl http://localhost:7071/api/Echo?message=Hello
You said 'Hello'

そして docker build -t okazuki/serverless2018:v1と打ち込んで docker でパッケージングします。

そして、docker run -p 8080:80 okazuki/serverless2018:v1と打ち込んで実行しましょう。docker psをして動いてるのを確認!

CONTAINER ID        IMAGE                       COMMAND                  CREATED             STATUS       PORTS                  NAMES
2ce0a0ec453c        okazuki/serverless2018:v1   "/bin/sh -c /azure-f…"   About an hour ago   Up 9 seconds        0.0.0.0:8080->80/tcp   gallant_hugle

先ほどと同じように curl コマンドで docker run で指定した 8080 ポートに対して関数のエンドポイントを叩いてみます。

>curl http://localhost:8080/api/Echo?message=Hello
You said 'Hello'

ばっちりですね。docker kill で止めておきましょう。

ここまで出来たら後はお好みの環境で動かすだけですね。 まぁ Azure Functions の機能自体が Azure Storage に依存してたりするので(Durable Functions とか) そういうのを使おうとすると結局 Azure のサービスが必要でレイテンシを考えると Azure でホストするしかないのかぁ…となりそうな気もします。

でも、Azure Functions はバインディング(トリガーやアウトプットやインプット)は自作出来るようになってるので例えば AWS 系サービスのトリガーとかを作ったらいい感じに AWS 上で Azure Functions を動かすことも出来るかもしれませんね。

IoT デバイスで Azure Functions を動かすのも Docker を使ってるので、Azure Functions の Docker 対応は要注目な気がします。

まとめ

Azure Functions は Docker イメージ作れるのでどこでも動く。

Azure Functions に興味がある人は、無料で教育用の Azure 環境をクレカ不要で使いながらテキストを進めることが出来る Microsoft Learn というサイトがあるので、そこから試してみてください。因みに無料枠でも Azure Functions 出来ます。

docs.microsoft.com

azure.microsoft.com

誰か

Azure Functions on AWS とかしてくれないかな?

Azure Functions on AWS

$
0
0

これは Serverless2 Advent Calendar 2018 の 4 日目の記事です。

qiita.com

前日は miyake さんによる Serverlessconf Tokyo で Durable Functions にコントリビュートしました | PaaSがかりの部屋でした。

本文

先日、Azure Functions を Docker を使って Docker イメージにパッケージングしました。

blog.okazuki.jp

Advent Calendar も、空白があるようなので折角なので、今回は AWS の Amazon ECS にデプロイしてみようと思います。

とりあえず手動 docker pushがだるかった(and 何故か自宅からやると凄く重かった)ので、以下のような azure-pipelines.ymlを書いて Azure DevOps (旧 Visual Studio Team Services) で docker builddocker pushをしてもらうようにしました。 とりあえず現在のビルド ID をタグにしたイメージと、同じものを latest タグに突っ込むようにしたけどいいのかな。

pool:vmImage:'Ubuntu 16.04'variables:imageName:'$(dockerId)/serverless2018:$(build.buildId)'latestImageName:'$(dockerId)/serverless2018:latest'steps:- script: |
    docker build -f Dockerfile -t $(imageName) .
    docker tag $(imageName) $(latestImageName)
  displayName:'docker build'- script: |
    docker login -u $(dockerId) -p $(pswd)
    docker push $(imageName)
    docker push $(latestImageName)
  displayName:'docker push'

変数部分は、別途変数用の設定画面でさくっと設定してビルド設定をしておきます。このビルドを走らせると以下のようにさくっと docker pushされました。 (自宅で何時間放置しても終わらなかったのに…)

Docker は初心者なのでよくわかってないのですが Tags ってところに作られてるから大丈夫なのかな。

f:id:okazuki:20181204111539p:plain

デプロイ to AWS

はじめて、ちゃんと開いた AWS のコンソールで ECS を探します。こんな感じに入力して更新

f:id:okazuki:20181204111947p:plain

タスク定義はそのままで、ロードバランサーは Application Load Balancer を選びました。あとは Yes マンです。 知らないクラウドの、いったい今何が作られてるんだろう…という不安感はたまらないですね(後で全部ちゃんと消せるんだろうか)

f:id:okazuki:20181204112208p:plain

作成されたロードバランサーを見ると凄く長い URL が生成されてます。そこに向けて curl コマンドを叩くと…。

>curl http://ec2co-ecsel-1g11swshqzpgu-1462563513.us-west-2.elb.amazonaws.com/api/Echo?message=Hello
You said 'Hello'

動いた!!

まとめ

まだプレビュー段階ですが Azure Functions の Docker サポートを使うと AWS で Azure Functions を動かすことも出来ますね!やったね。

.NET Core 3.0 でのデスクトップ開発

$
0
0

先日の Connect(); 2018 で .NET Core 3.0 Preview 1 が出ましたね!そして、前々から噂されてた WPF / Windows Forms のサポートが試せます。まぁ、前々から alpha 版とか使って試せましたが alpha はちょっと…という感じでも Preview なら許容範囲かな?という人は入れて試してみましょう。

alpha のころに試した記事は以下になります。

blog.okazuki.jp

試してみた

.NET Core 3.0 preview を入れたら以下のコマンドで WPF や Windows Forms のプロジェクトを作れます。

dotnet new wpf
dotnet new winforms

そして、Visual Studio 2019 Preview で開いて開発出来ます。

f:id:okazuki:20181205152507j:plain

f:id:okazuki:20181205152536j:plain

デザイナーは、まだサポートされてませんが実行してデバッグすることが出来ます。

f:id:okazuki:20181205152627j:plain

.NET Framework のライブラリって使えるの?

WPF や WinForms アプリは、ほぼほぼ .NET Framework をターゲットにしたライブラリに依存してると思います。.NET Core 3.0 自体は .NET Standard をサポートしてるので、既存の .NET Standard 対応のライブラリは使えるのですが .NET Framework をターゲットにした奴は使えるの?という話し。 例えば Prism とかですね。

一応 NuGet Package Manager から追加は出来ますが、互換性に関する警告が出ます。

f:id:okazuki:20181205152815j:plain

実際どうなの?というところは .NET Portability Analyzer で確認できます。

docs.microsoft.com

VS 2019 ではこれの拡張機能が、まだサポートされてないのでコマンドラインツールを以下から入れて使う感じになります。

github.com

インストール後に、プロジェクトの出力フォルダで以下のコマンドを実行するとチェックが走ります。

<Path to APIPort.exe folder>\ApiPort.exe analyze -f .

実行結果として以下のようなレポートが出ています。

f:id:okazuki:20181205153120j:plain

このレポートを見て、問題なさそうだと思ったら警告が出ないように VS2019 のプロパティウィンドウで警告抑止が出来ます。

f:id:okazuki:20181205153201j:plain

まとめ

.NET Core 3.0 は、まだプレビューですがとりあえず今すぐ試すことが出来るようになっています。 そして、自分たちのアプリケーションが .NET Core 3.0 だとどうなんだろう?というのを確認出来ます。

.NET Core 3.0 は、一般的に .NET Framework よりも性能がいいというメリットや、配布でも自己完結型の展開(SCD)を使うことで、アプリに .NET Core 3.0 を含めた形で展開することが出来ます。つまり、配布先の OS が最新のフレームワークを入れてなくてもフォルダーをコピーすれば動くようにパッケージング出来ます。(OS 管理者にランタイムのパッチ適用や更新をお願いするのではなく、アプリ開発者側が面倒みるというスタンス)

docs.microsoft.com

実際に、最初に作った WPF アプリを自己完結型として発行して zip 圧縮すると 40MB くらいになりました。そして、それを Azure 上に作成した仮想マシンにコピー(こっちには .NET Core 3.0 を入れてません)して実行するとばっちり動きました。

f:id:okazuki:20181205160624j:plain


Livet の .NET Core 3.0 Preview 1 向けの NuGet パッケージと VS2019 Preview の拡張機能を作ってみました

$
0
0

.NET Core 3.0 向けのパッケージ作るのがどれくらい大変なのか試すついでにやってみました。結果は思った以上にさくっと出来ました。

NuGet はこちら。

www.nuget.org

VS2019 向けの vsix は GitHub のリリースページからダウンロードできます。

github.com

使ってる画面はこんな感じです。

f:id:okazuki:20181206180013p:plain
プロジェクトの作成

f:id:okazuki:20181206180115p:plain
コードスニペット

f:id:okazuki:20181206181552p:plain
VS2019 Preview で Livet の .NET Core 3.0 版を使って .NET Core 3.0 Preview1 の WPF で開発してる様子

Azure Functions v2 で Python がサポートされました(プレビュー)

$
0
0

プレビューだけど、Azure Functions で Python のサポートが追加されました。

英語ですが、ドキュメントもちゃんとありますね!

docs.microsoft.com

ドキュメントにも記載がありますが現在サポートされている Python のバージョンは 3.6.x みたいです。3.7 ではないみたいですね。私のローカルには Python 3.6.2 が入ってたのですが折角なので最新の 3.6.7 を以下のサイトからダウンロードしてインストールしました。

www.python.org

C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36C:\Users\ユーザー名\AppData\Local\Programs\Python\Python36\Scripts\にパスを通して以下のコマンドで確認してみます。(インストール時にパス通すチェックボックスがあるみたいなのですが、さくっとインストールしたので見逃してました)

>>python --version
Python 3.6.7

私の環境は Windows なのですが、Azure Functions で Python を動かすときは Linux がベースの OS みたいなので、実際に開発するときは Linux でしたほうが幸せな気がしてます。

Visual Studio Code に Azure Functions と Python の拡張機能を入れて準備完了です。func initコマンドをうつか、Visual Studio Code でフォルダを開いて左側のアイコンの並びで Azure アイコンをクリックすると FUNCTIONS というものがあるので、Create new project をクリックして GUI でも可能です。

f:id:okazuki:20181211111855p:plain

どちらの手順でも言語を選ぶようになるので Python を選びましょう。

プロジェクトが出来たら、続けて func newか Visual Studio Code で Create Function をクリックして関数を作ります。

f:id:okazuki:20181211113704p:plain

Http Trigger で、Echo という名前でさくっと作ってみました。Authorization level は Function (関数単位でアクセスするためのキーが出来る)を選びました。

そうすると、__init__.pyというファイル名で以下のようなコードが生成されます。

import logging

import azure.functions as func


defmain(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    name = req.params.get('name')
    ifnot name:
        try:
            req_body = req.get_json()
        exceptValueError:
            passelse:
            name = req_body.get('name')

    if name:
        return func.HttpResponse(f"Hello {name}!")
    else:
        return func.HttpResponse(
             "Please pass a name on the query string or in the request body",
             status_code=400
        )

Python にも Java でいう Annotation や C# でいう Attribute が無さそうなので、node と同じように function.json に関数のトリガーや入力や出力を定義する形みたいです。以下のような function.json が生成されていました。

{"scriptFile": "__init__.py",
  "bindings": [{"authLevel": "function",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get",
        "post"
      ]},
    {"type": "http",
      "direction": "out",
      "name": "$return"
    }]}

おもむろに func startF5を押して実行してみます。Echo 関数のエンドポイントが表示されるので適当なツールなりブラウザで叩いてみます。今回は curl 使いました。実行するとちゃんと動いてますね!

> curl http://localhost:7071/api/Echo?name=okazuki


StatusCode        : 200
StatusDescription : OK
Content           : Hello okazuki!

カスタマイズとか

init.py の main 関数が呼び出されるのがデフォルトなのですが、これは function.json でカスタマイズ出来ます。

scriptFileentoryPointで指定する感じです。ドキュメントのこの部分に書いてあります。

docs.microsoft.com

共通ロジックとか

プロジェクトの下に SharedCode とかいうフォルダ(名前は任意)を作って、その中に logic.py とかいうファイルを作って以下のような関数を定義しました。

defgenerateGreetingMessage(name: str) -> str:
    return"Hello " + name

そして、__init__.pyを以下のように書き換えてみます。

import logging

from ..ShreadCode.logic import generateGreetingMessage
import azure.functions as func


defmain(req: func.HttpRequest) -> func.HttpResponse:
    logging.info('Python HTTP trigger function processed a request.')

    name = req.params.get('name')
    ifnot name:
        try:
            req_body = req.get_json()
        exceptValueError:
            passelse:
            name = req_body.get('name')

    if name:
        return func.HttpResponse(generateGreetingMessage(name))
    else:
        return func.HttpResponse(
             "Please pass a name on the query string or in the request body",
             status_code=400
        )

実行同じように実行するとちゃんと動きました!

> curl http://localhost:7071/api/Echo?name=okazuki

StatusCode        : 200
StatusDescription : OK
Content           : Hello okazuki

Azure Functions の拡張機能を使う

例えば Azure Storage の QueueTrigger なんかを使おうとしたときには拡張機能を入れます。その時は以下のようなコマンドをうちます。

func extensions install --package Microsoft.Azure.WebJobs.Extensions.Storage --version 3.0.2

詳細はここらへんに書いてあります。

docs.microsoft.com

試しに QueueTrigger で関数を作ります。func newか VS Code の Create Functionで。

適当に入力を進めていくと以下のような関数が作られます。

import logging

import azure.functions as func


defmain(msg: func.QueueMessage) -> None:
    logging.info('Python queue trigger function processed a queue item: %s',
                 msg.get_body().decode('utf-8'))

function.json は以下のような感じ。

{"scriptFile": "__init__.py",
  "bindings": [{"name": "msg",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "python-queue-items",
      "connection": "AzureWebJobsStorage"
    }]}

とりあえず、Queue に投げ込まれたメッセージを出力するだけみたいですね。 local.settings.json を開くと AzureWebJobsStorage という項目があるので、そこの値を UseDevelopmentStorage=trueにして、エミュレーターにいくようにしておきます。 ローカルで実行して、Azure Storage Explorer あたりで適当に Queue にメッセージを投げ込んでみました。

f:id:okazuki:20181211123056p:plain

ばっちり!

[12/11/2018 3:31:59 AM] Trigger Details: MessageId: c9f676c5-91d9-4a50-81a0-c4ca5d4531dc, DequeueCount: 1, InsertionTime: 12/11/2018 3:28:40 AM +00:00
[12/11/2018 3:31:59 AM] Python queue trigger function processed a queue item: {
  "message": "Hello world"
}
[12/11/2018 3:31:59 AM] Executed 'Functions.QueueFunc' (Succeeded, Id=0ac16f9a-6594-49ca-ae9b-d0d11528b416)

デプロイ

そのままデプロイしてみました。VS Code だと Deploy to Function App を押すだけ。 私は zip をパッケージから実行する方法でデプロイしてみました。

docs.microsoft.com

ポータルでもばっちり関数が見えてます。

f:id:okazuki:20181211130715p:plain

ポータルから関数の URL を取得するとクリックすると関数の URL が認証キー付きでゲットできるので叩いてみましょう。

f:id:okazuki:20181211131324p:plain

curl コマンドで叩いてみました。

> curl "https://pyfunckaota.azurewebsites.net/api/Echo?code=キーの値&name=MicrosoftAzure"


StatusCode        : 200
StatusDescription : OK
Content           : Hello MicrosoftAzure

いい感じですね。

まとめ

Java と Python 早く GA しないかなぁ。

Azure Functions の Durable Functions の node 版がリリースされたので遊んでみました

$
0
0

Durable Functions は、個人的に Azure で一番好きな機能なのですが、それが node でも使えるようになりました。 これまでもプレビューであったけど、今回は正式版ということで実践投入行ける感じですね。

これまで、Durable Functions を使おうと思ったら C# でしたが、これからは node でも OK。個人的には C# の方がなじむけど、好きな方が使える状態なのは大事ですね。

使ってみよう

では行ってみましょう。適当なフォルダで func initか VS Code で Create project して JavaScript を選びます。npm init -yもしておきます。そして npm i durable-functionsでライブラリを入れておきます。

Durable Functions の拡張機能もコマンドで有効にしておきましょう。

func extensions install -p Microsoft.Azure.WebJobs.Extensions.DurableTask -v 1.7.0

続けて関数を作っていきます。Durable Functions を作るときは、オーケストレーターを起動するための関数(HTTPやQueueなど)と、色んな処理を呼び出したりフローを管理するオーケストレーター関数と、実際の処理を行うアクティビティ関数の最低 3 つをよく作ります。アクティビティ関数は、やりたい処理の数だけできるので実際にはもっと沢山の関数を定義します。

Durable Functions のいいところは、これらの沢山の関数をサーバーレスアーキテクチャーらしい呼び出し方をしてくれてるのに、書き味は普通のプログラムと変わらないというところが素晴らしいところです。 例えば、処理を 3 つシーケンシャルに呼ぶときには、サーバーレスとかだと Queue を間に挟んで 1 つ 1 つを細かくわけてやることが多いです。まぁ本当に小さい処理なら以下のように

funcA();
funcB();
funcC();

とすればいいのですが、そこそこ大きな処理になってくると長い実行時間だと色々困ることがあるサーバーレスのプラットフォーム上で動かすときは

Client(処理のお願いを QueueA に投げる) -> QueueA -> funcA(QueueA をトリガーに処理をして結果を QueueB に投げる) -> QueueB -> funcB(QueueB をトリガーに処理をして結果を QueueC に投げる) -> QueueC -> funcC(QueueC をトリガーに処理をして結果を QueueD に投げる) -> QueueD -> Client(QueueD を監視して結果を受け取る)

ツライ。この辛さを解消してくれて、さらに有り余るメリットを与えてくれるのが Durable Functions だと思ってます。

脱線しましたが、気を取り直して関数を作成します。まずはトリガーとなる関数ですね。今回は HttpTrigger で作成します。 func newか VS Code で New Functionを選んで作ります。

function.jsonに追記して Durable Functions の機能を使えるようにします。

{"disabled": false,
  "bindings": [{"authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["get",
        "post"
      ]},
    {"type": "http",
      "direction": "out",
      "name": "$return"
    },
    {"name": "starter",
      "type": "orchestrationClient",
      "direction": "in"
    }]}

type が orchestrationClient の定義がそれにあたります。index.jsではオーケストレーターを呼び出す処理を書きます。

const df = require('durable-functions');
module.exports = async function (context, req) {const client = df.getClient(context);
    const instanceId = await client.startNew("OrchestrationFunction", undefined, req.body);
    return client.createCheckStatusResponse(req, instanceId);
};

戻り値は、状態を確認したりするための情報をクライアントに返すようにていします。後で使いますが、これも便利。OrchestrationFunction には HTTP のリクエストのボディを入力として渡しています。

では、オーケストレーターの関数を作りましょう。HttpTrigger の関数を OrchesrationFunction という名前で作ります。そして function.json を以下のように編集します。

{"disabled": false,
  "bindings": [{"name": "context",
      "type": "orchestrationTrigger",
      "direction": "in"}]}

そして、処理を書いていきます。このオーケストレーターの関数は、実は何回も呼び出されて裏で実行履歴と突き合わせをして動く特殊な関数なので、ちょっと書くときにお作法がいります。要は何回動かしても同じ結果になるようなものしか使えません。(乱数とかはダメ、そういうのがしたい場合は Durable Functions が提供してる代替関数を使う)

const df = require('durable-functions');
module.exports = df.orchestrator(function* (context) {
    context.df.setCustomStatus({ message: 'OrchestrationFunction started'});
    const output = [];
    output.push(yield context.df.callActivity('SayHello', context.df.getInput().first));
    context.df.setCustomStatus({ message: 'first activity is completed'});
    output.push(yield context.df.callActivity('SayHello', context.df.getInput().second));
    context.df.setCustomStatus({ message: 'second activity is completed'});
    output.push(yield context.df.callActivity('SayHello', context.df.getInput().third));
    context.df.setCustomStatus({ message: `third activity is completed, waiting accept event: ${JSON.stringify(output)}`});
    const accept = context.df.waitForExternalEvent('accept');
    const reject = context.df.waitForExternalEvent('reject');

    constevent = yield context.df.Task.any([accept, reject]);
    if (event === accept) {return output;
    }else{return[];
    }});

さて、見慣れない関数の連続ですが、やってることはこの関数に渡された入力の first, second, third を、SayHello というアクティビティに渡して結果を output に追加していっています。

途中経過を連絡するためにカスタムステータスを適時設定していってます。

最後に、外部からの accept か reject という名前のイベントを待って accept されたら実行結果を返して、reject されたら空の配列を返しています。

最後に SayHello 関数を作ります。これも HttpTrigger で作ったあとに function.json を書き換えます。(そのうち func new とかにテンプレートが追加されると思いますが今はない)

{"disabled": false,
  "bindings": [{"name": "name",
      "type": "activityTrigger",
      "direction": "in"}]}

index.js は以下のような感じ。

function sleep10s() {returnnew Promise(resolve => setTimeout(resolve, 10 * 1000));
}

module.exports = async function (context, name) {
    await sleep10s();
    return `Hello ${name}`;
};

10 秒待った後に Hello xxxx という文字を返します。

ということで、まとめると30秒くらい処理に時間がかかって、さらに外部からのイベントが来たら結果を返すという感じですね。SayHello は 1 つ 1 つが 10 秒なので、どれくらいの時間がかかるか読めますが外部からのイベントは Durable Functions が提供してくれる REST API を叩かないと発行出来ないので、どれくらい時間がかかるのかさっぱりわかりません。

そんな処理も Durable Functions でさくっと書けるのが素敵。

実行してみよう

local.settings.jsonで Azure Storage の接続文字列を入れます。macOS や Linux では公式では Azure にストレージアカウントを作って、その接続文字列を入れてって書かれてます。Windows の場合はエミュレーターでもいいので UseDevelopmentStorage=trueでも OK です。

あと、Azure の Storage Emulator のバージョンによっては AzureWebJobsSecretStorageType を追加しないといけないみたいです。

{"IsEncrypted": false,
  "Values": {"AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsSecretStorageType": "files"}}

Issue はこちら。

github.com

このブログから行きつきました。感謝。

qiita.com

さ・ら・に。ローカルで動かすときは以下の issue もあります。

github.com

ということで以下のように WEBSITE_HOSTNAME環境変数に localhost:port番号を追加します。デフォルトだとポート番号は 7071 なので以下のような感じになると思います。

f:id:okazuki:20181211180159p:plain

では、func startで実行して HttpTrigger を叩きます。

f:id:okazuki:20181211181340p:plain

実行結果に、statusQueryGetUri というのがあります。これを叩くと関数の状態をチェックできます。

f:id:okazuki:20181211181620p:plain
関数実行直後に叩いた様子

f:id:okazuki:20181211181653p:plain
数十秒後に叩いた様子

カスタムステータスがイベントを待ってるようなので、イベントを送りたいと思います。このときも最初に HttpTrigger を叩いた結果に sendEventPostUriというのがあります。 今回の場合はこんな URL です。

http://localhost:7071/runtime/webhooks/durabletask/instances/98fbec63c5124414ae363518bb7d27bc/raiseEvent/{eventName}?taskHub=DurableFunctionsHub&connection=Storage&code=mF4BzJbHO9xX51OE6JK76feSO8A4zsUewXm53e9lSUxo5LCmwaYe6w==

{eventName} というところに今回は accept か reject を指定して POST でボディが空の JSON のリクエストを送ると OK です。

こんな感じ。

f:id:okazuki:20181211182105p:plain

この API を叩いたあとに結果を取得するとこの通りステータスが Completed になって output に結果が入っています。

f:id:okazuki:20181211182215p:plain

ソース

書きながら作ったソースは以下に置いてます。

github.com

.NETCore CLI コマンドを実行するときの .NET Core SDK を明示的に指定する

$
0
0

何もしないと最新を使うので普段は何もしなくていい。 でも、今自分は .NET Core 3.0 Preview 1 を入れてるので出来れば使われたくないようなケースもある!

ということで、その場合にどうするかです。

使いたい SDK のバージョンを見る

今のマシンに入ってる SDK は以下のコマンドで見れるのでチェック

> dotnet --list-sdks
2.1.202 [C:\Program Files\dotnet\sdk]
2.1.500 [C:\Program Files\dotnet\sdk]
2.1.502 [C:\Program Files\dotnet\sdk]
2.1.600-preview-009426 [C:\Program Files\dotnet\sdk]
2.2.100 [C:\Program Files\dotnet\sdk]
2.2.200-preview-009648 [C:\Program Files\dotnet\sdk]
3.0.100-preview-009812 [C:\Program Files\dotnet\sdk]

なんかいっぱい入ってますね…

今回は 2.2.100 を使う用に指定したいと思います。

global.json を作る

global.json というファイルで指定します。スキーマとしてはシンプルで以下のような感じです。

{"sdk": {"version": "2.1.300"
  }}

コマンドで生成できます。以下のコマンドを SDK のバージョンを指定したいプロジェクトのフォルダで実行します。

dotnet new globaljson --sdk-version 2.2.100

これで .NET Core 3.0 じゃなくて .NET Core 2.2.100 の SDK が使われるようになりました。 めでたしめでたし。

ReactiveProperty v5.3.1 をリリースしました

$
0
0

System.Reactive のバージョン上げました。 あと、不要な依存関係があったのを削除したのと、ドキュメントコメントが全部の public メソッドに追加されてます。

詳しいことは GitHub のリリースに書いています。

Release v5.3.1 · runceel/ReactiveProperty · GitHub

NuGet に上げてるのでインストールと更新はこちらからどうぞ。

www.nuget.org

Viewing all 1388 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>