LoginSignup
11
14

More than 5 years have passed since last update.

Azure Functionsでサーバーサイドアプリを動かす

Posted at
1 / 23

When

2016/12/02

At

Gotanda.js #6 in Oisix


今回のGitHubリポジトリ

ovrmrw/qiita-advent-calendar-2016-microservices-1

現在更新中:innocent:

===

話す内容

  • Azure Functionsの基本。
  • Azure Functionsでサーバーサイドアプリを動かす。

自己紹介

ちきさん
(Tomohiro Noguchi)

Twitter: @ovrmrw

ただのSIer。

Angular Japan User Group (ng-japan)スタッフ。

3a2512bb-aa72-4515-af42-1f1721252f39.jpg


アカウントIDの由来

  1. the day after tomorrow
  2. overmorrow(俗語)
  3. 略して
  4. ovrmrw
  5. 「先を見据えて」よりさらに先を、みたいな感じです。

(よく聞かれるので:innocent:)


(ここから本編)


Azure Functionsの基本から

  • AWS Lambda + API Gatewayみたいなもの。
  • (Herokuみたいな)Web Appsと同じ仕組みのVMを起動するのでAWS Lambdaより遅い。(多分)
  • ストレージIOがとても遅いのでモジュールを多数importする場合はWebpackでバンドルすべき
  • Node.js以外にもC#とかF#とかでも書けるらしい。

Rule1. サブフォルダの名前がエンドポイントになる。

root
|--function1
|  |--function.json
|  |--main.ts
|--function2
   |--function.json
   |--main.ts

生成されるエンドポイント。

  • https://my-azure-functions.azurewebsites.net/api/function1
  • https://my-azure-functions.azurewebsites.net/api/function2

Rule2. ファンクションフォルダにはfunction.jsonが必須。

HTTPエンドポイントの場合はこのように書く。
function.jsonが存在しないとファンクションと見なされない。

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

Rule3. contextを引数に取る関数をexportする。

最高にシンプルなファンクションはこんな感じ。

main.ts
export function azureFunction(context, req) {
  context.res = {
    status: 200,
    body: {
      message: 'Hello world.',
    }
  };
  context.done();
}

Rules

  • exportする関数は最低限contextを引数に取ること。
  • context.resstatusbodyをセットして返すこと。
  • 最後にcontext.done()に呼ぶこと。(あるいはPromiseを返すこと)
main.ts(Promise)
export async function azureFunction(context, req) {
  context.res = {
    status: 200,
    body: {
      message: 'Hello world.',
    }
  };
  // context.done(); <-- Promiseを返す場合はdone()は呼ばなくていい。
}

Rule3-2. 複数ファイルで構成される場合はindex.tsを用意しておくとトラブルがない。

例えばこのような状況ではindex.tsを作っておかないとNot Found沼に嵌ることがある。

root
|--function1
   |--function.json
   |--index.ts
   |--main.ts
   |--server.ts

index.tsはファンクションをimportしてexport defaultでexportするだけで良い。

index.ts
import { azureFunction } from './main';

export default azureFunction;

(ここまでが基本編)


Azure Functionsのエンドポイントでサーバーサイドアプリ(Express, Hapi等)を動かすことができる。


Server-side app on Azure Functions

root
|--hapi-simple
   |--function.json
   |--index.ts
   |--main.ts
   |--server.ts

(example)

  • https://my-azure-functions.azurewebsites.net/api/hapi-simple
    • { message: 'Hello world'}
  • https://my-azure-functions.azurewebsites.net/api/hapi-simple/Gotanda.js
    • { message: 'Hello world, Gotanda.js'}

function.jsonにちょっと細工

"route":"hapi-simple/{*segments}"を書くことでルーティングが可能になる。

function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route":"hapi-simple/{*segments}"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "disabled": false
}

server.ts

サーバーサイド(Hapi)のコードをそのまま持ち込んで、URIを生成する関数をPromiseでexportする。

server.ts
import * as Hapi from 'hapi';

const server = new Hapi.Server();
server.connection({
  host: 'localhost'
});

server.route([
  {
    method: 'GET',
    path: '/',
    handler: (req, reply) => {
      const message = 'Hello world.';
      reply({ message });
    }
  },
  {
    method: 'GET',
    path: '/{name}',
    handler: (req, reply) => {
      const message = 'Hello world, ' + req.params['name'];
      reply({ message });
    }
  }
]);


let uri: string | undefined;

export function createUri(): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    if (uri) {
      resolve(uri);
    } else {
      server.start((err) => {
        if (err) { reject(err); }
        uri = server.info.uri;
        console.log('Server running at:', uri);
        resolve(uri);
      });
    }
  });
}

main.ts

Hapiサーバー起動後のURIを取得して、ファンクションに入ってきたrequestオブジェクトをリレーしてHapiサーバーにfetchする。

main.ts
import { customFetch } from '../lib/utils';
import { createUri } from './server';

export async function azureFunction(context, req) {
  try {
    const uri: string = await createUri();
    const res = await customFetch(uri, req);

    if (res.status === 200) {
      context.res = {
        status: res.status,
        body: res.myBody,
        headers: res.myHeaders,
      };
    } else { // Not Found
      context.res = {
        status: res.status,
        body: res.statusText,
      }
    }
  } catch (err) {
    context.res = {
      status: 500,
      body: err,
    };
  }
}

index.ts

main.tsで定義したファンクションをexport defaultでexportしているだけ。

index.ts
import { azureFunction } from './main';

export default azureFunction;

1ドメインでこんなことも可能です。

root
|--express1
|  |--function.json
|  |--index.ts
|  |--main.ts
|  |--server.ts
|--express2
|  |--function.json
|  |--index.ts
|  |--main.ts
|  |--server.ts
|--express3
   |--function.json
   |--index.ts
   |--main.ts
   |--server.ts

生成されるエンドポイント。

  • https://my-azure-functions.azurewebsites.net/api/express1
  • https://my-azure-functions.azurewebsites.net/api/express2
  • https://my-azure-functions.azurewebsites.net/api/express3

イメージ図
Server-side app on Azure Functions.png


(動作デモ)


  • まだJSONを返すAPIしか試作してないけど、HTMLを返すこともできるのでは?
  • つまりAngular SSR on Azure Functionsをやりたい。
  • ここ数日トライし続けているが光が見えない。
  • 誰か作れたら教えてください。

===

  • (最新情報) HTMLは返せた。でも画像が返せない。もうAzureの仕様なのでは…
  • 画像は全て別サーバーから取得するようにしたらイケるかもしれない。

Thanks!

11
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
14