Azure Functions 自訂處理常式

每個 Functions 應用程式都是由特定語言處理常式執行。 雖然 Azure Functions 預設會提供許多語言處理常式,但在某些情況下,您可能會想要使用其他語言或執行階段。

自訂處理常式是輕量網頁伺服器,其會從 Functions 主機接收事件。 任何支援 HTTP 基元的語言都可以實作自訂處理常式。

自訂處理常式最適合以下情況:

  • 在目前未現成提供的語言 (例如 Go 或 Rust) 中實作函式應用程式。
  • 在目前未預設提供的執行階段 (例如 Deno) 中實作函式應用程式。

透過自訂處理常式,您可以透過延伸模組套件組合使用觸發程序和輸入和輸出繫結

使用 Go 和 Rust 中的快速入門,開始使用 Azure Functions 自訂處理常式。

概觀

下圖顯示 Functions 主機與實作為自訂處理常式的網頁伺服器之間的關聯性。

Azure Functions custom handler overview

  1. 每個事件都會觸發傳送至 Functions 主機的要求。 事件是 Azure Functions 支援的任何觸發程序。
  2. 然後 Functions 主機會向網頁伺服器發出要求承載。 承載會保存函式的觸發程序和輸入繫結資料與其他中繼資料。
  3. 網頁伺服器會執行個別函式,並將回應承載傳回至 Functions 主機。
  4. Functions 主機會將來自回應的資料傳遞至函式的輸出繫結進行處理。

實作為自訂處理常式的 Azure Functions 應用程式必須根據一些慣例來設定 host.jsonlocal.settings.jsonfunction.json 檔案。

應用程式結構

若要實作自訂處理常式,您需要應用程式的下列層面:

  • 應用程式根目錄中有 host.json 檔案
  • 應用程式根目錄中有 local.settings.json 檔案
  • 每個函式的 function.json 檔案 (在符合函式名稱的資料夾內)
  • 執行網頁伺服器的命令、指令碼或可執行檔

針對名為 "MyQueueFunction" 的函式以及名為 handler.exe 的自訂處理常式可執行檔,下圖顯示這些檔案在檔案系統看起來的樣子。

| /MyQueueFunction
|   function.json
|
| host.json
| local.settings.json
| handler.exe

組態

應用程式是透過 host.jsonlocal.settings.json 檔案來設定。

host.json

host.json 會透過指向能夠處理 HTTP 事件的網頁伺服器,告知 Functions 主機向何處傳送要求。

自訂處理常式是藉由設定 host.json 檔案來定義,其中包含如何透過 customHandler 區段執行網頁伺服器的詳細資料。

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  }
}

customHandler 區段會指向由 defaultExecutablePath 所定義的目標。 執行目標可以是實作網頁伺服器所在的命令、可執行檔或檔案。

使用 arguments 陣列將任何引數傳遞至該可執行檔。 引數支援使用 %% 標記法 (應用程式設定) 擴充環境變數。

您也可以使用 workingDirectory 來變更可執行檔所使用的工作目錄。

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "app/handler.exe",
      "arguments": [
        "--database-connection-string",
        "%DATABASE_CONNECTION_STRING%"
      ],
      "workingDirectory": "app"
    }
  }
}
繫結支援

參考 host.json 檔案中的延伸模組套件組合,即可使用標準觸發程序和輸出繫結。

local.settings.json

local.settings.json 會定義在本機執行函式應用程式時所使用的應用程式設定。 因為其可能包含秘密,應該從原始檔控制中排除 local.settings.json。 在 Azure 中,請改用應用程式設定。

針對自訂處理常式,請在 local.settings.json 中將 FUNCTIONS_WORKER_RUNTIME 設定為 Custom

{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "Custom"
  }
}

函式中繼資料

搭配自訂處理常式使用時, function.json 內容與在任何其他內容下定義函式的方式並無不同。 唯一的需求是 function.json 檔案必須位於指定的資料夾中,以符合函式名稱。

下列 function.json 會設定具有佇列觸發程序和佇列輸出繫結的函式。 因為它位於名為 MyQueueFunction 的資料夾,其會定義名為 MyQueueFunction 的函式。

MyQueueFunction/function.json

{
  "bindings": [
    {
      "name": "myQueueItem",
      "type": "queueTrigger",
      "direction": "in",
      "queueName": "messages-incoming",
      "connection": "AzureWebJobsStorage"
    },
    {
      "name": "$return",
      "type": "queue",
      "direction": "out",
      "queueName": "messages-outgoing",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

要求承載

收到佇列訊息時,Functions 主機會將 HTTP post 要求傳送至內文中具有承載的自訂處理常式。

下列程式碼代表範例要求承載。 承載包含的 JSON 結構具有兩個成員:DataMetadata

Data 成員包含符合 function.json 檔案的繫結陣列中定義的輸入和觸發程序名稱的金鑰。

Metadata 成員包含從事件來源產生的中繼資料

{
  "Data": {
    "myQueueItem": "{ message: \"Message sent\" }"
  },
  "Metadata": {
    "DequeueCount": 1,
    "ExpirationTime": "2019-10-16T17:58:31+00:00",
    "Id": "800ae4b3-bdd2-4c08-badd-f08e5a34b865",
    "InsertionTime": "2019-10-09T17:58:31+00:00",
    "NextVisibleTime": "2019-10-09T18:08:32+00:00",
    "PopReceipt": "AgAAAAMAAAAAAAAAAgtnj8x+1QE=",
    "sys": {
      "MethodName": "QueueTrigger",
      "UtcNow": "2019-10-09T17:58:32.2205399Z",
      "RandGuid": "24ad4c06-24ad-4e5b-8294-3da9714877e9"
    }
  }
}

回應承載

依慣例,函式回應會格式化為金鑰/值組。 支援的金鑰包括:

承載金鑰 資料類型 備註
Outputs object 保留 function.jsonbindings 陣列所定義的回應值。

例如,如果設定函式具有名為 "myQueueOutput" 的佇列輸出繫結,則包含名為 myQueueOutput 的金鑰的 Outputs,其會由自訂處理常式設定為傳送至佇列的訊息。
Logs 陣列 訊息會出現在 Functions 叫用記錄中。

在 Azure 中執行時,訊息會出現在 Application Insights 中。
ReturnValue string 用於在 function.json 檔案中將輸出設定為 $return 時提供回應。

這是回應承載的範例。

{
  "Outputs": {
    "res": {
      "body": "Message enqueued"
    },
    "myQueueOutput": [
      "queue message 1",
      "queue message 2"
    ]
  },
  "Logs": [
    "Log message 1",
    "Log message 2"
  ],
  "ReturnValue": "{\"hello\":\"world\"}"
}

範例

自訂處理常式可以在支援接收 HTTP 事件的任何語言中實作。 下列範例示範如何使用 Go 程式設計語言來實作自訂處理常式。

具有繫結的函式

在此範例中實作的案例包含名為 order 的函式,其會接受具有代表產品訂單之承載的 POST。 當訂單張貼至函式時,會建立佇列儲存體訊息,並傳回 HTTP 回應。

實作

在名為 order 的資料夾中,function.json 檔案會設定 HTTP 觸發的函式。

order/function.json

{
  "bindings": [
    {
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "methods": ["post"]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    },
    {
      "type": "queue",
      "name": "message",
      "direction": "out",
      "queueName": "orders",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

此函式定義為 HTTP 觸發的函式,可傳回 HTTP 回應並輸出佇列儲存體訊息。

在應用程式的根目錄中,host.json 檔案會設定為執行名為 handler.exe 的可執行檔 (在 Linux 或 macOS 中為 handler)。

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[1.*, 2.0.0)"
  }
}

這是傳送至 Functions 執行階段的 HTTP 要求。

POST http://127.0.0.1:7071/api/order HTTP/1.1
Content-Type: application/json

{
  "id": 1005,
  "quantity": 2,
  "color": "black"
}

將後 Functions 執行階段會將下列 HTTP 要求傳送至自訂處理常式:

POST http://127.0.0.1:<FUNCTIONS_CUSTOMHANDLER_PORT>/order HTTP/1.1
Content-Type: application/json

{
  "Data": {
    "req": {
      "Url": "http://localhost:7071/api/order",
      "Method": "POST",
      "Query": "{}",
      "Headers": {
        "Content-Type": [
          "application/json"
        ]
      },
      "Params": {},
      "Body": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}"
    }
  },
  "Metadata": {
  }
}

注意

為了簡潔起見,已移除承載的某些部分。

handler.exe 是編譯的 Go 自訂處理常式程式,其會執行網頁伺服器,並回應來自 Functions 主機的函式叫用要求。

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

type InvokeRequest struct {
	Data     map[string]json.RawMessage
	Metadata map[string]interface{}
}

type InvokeResponse struct {
	Outputs     map[string]interface{}
	Logs        []string
	ReturnValue interface{}
}

func orderHandler(w http.ResponseWriter, r *http.Request) {
	var invokeRequest InvokeRequest

	d := json.NewDecoder(r.Body)
	d.Decode(&invokeRequest)

	var reqData map[string]interface{}
	json.Unmarshal(invokeRequest.Data["req"], &reqData)

	outputs := make(map[string]interface{})
	outputs["message"] = reqData["Body"]

	resData := make(map[string]interface{})
	resData["body"] = "Order enqueued"
	outputs["res"] = resData
	invokeResponse := InvokeResponse{outputs, nil, nil}

	responseJson, _ := json.Marshal(invokeResponse)

	w.Header().Set("Content-Type", "application/json")
	w.Write(responseJson)
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/order", orderHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

在此範例中,自訂處理常式會執行網頁伺服器來處理 HTTP 事件,並設定為透過 FUNCTIONS_CUSTOMHANDLER_PORT 接聽要求。

即使 Functions 主機在 /api/order 收到原始 HTTP 要求,仍會使用函式名稱 (其資料夾名稱) 叫用自訂處理常式。 在此範例中,函式定義於路徑 /order。 主機會在 /order 的路徑傳送 HTTP 要求給自訂處理常式。

POST 要求傳送至此函式時,觸發程序和函式中繼資料可透過 HTTP 要求本文取得。 原始 HTTP 要求本文可以在承載的 Data.req.Body 中存取。

函式的回應會格式化為金鑰/值組,其中的 Outputs 成員會保存 JSON 值,而金鑰會符合 function.json 檔案中所定義的輸出。

這是此處理常式傳回至 Functions 主機的範例承載。

{
  "Outputs": {
    "message": "{\"id\":1005,\"quantity\":2,\"color\":\"black\"}",
    "res": {
      "body": "Order enqueued"
    }
  },
  "Logs": null,
  "ReturnValue": null
}

藉由將 message 輸出設定為等於來自要求的訂單資料,函式會將訂單資料輸出至已設定的佇列。 Functions 主機也會向呼叫端傳回在 res 中設定的 HTTP 回應。

僅限 HTTP 的函式

針對沒有其他繫結或輸出的 HTTP 觸發函式,您可能希望處理常式直接使用 HTTP 要求和回應,而不是自訂處理常式要求回應承載。 您可以使用 enableForwardingHttpRequest 設定,在 host.json 中設定此行為。

重要

自訂處理常式功能的主要用途是啟用目前在 Azure Functions 上沒有第一級支援的語言和執行階段。 雖然可以使用自訂處理常式執行 Web 應用程式,但 Azure Functions 並非標準的反向 Proxy。 某些功能,例如回應串流、HTTP/2 和 WebSockets 會無法使用。 HTTP 要求的某些元件,例如某些標頭和路由可能會受到限制。 您的應用程式也可能經歷過多的冷啟動

若要解決這些情況,請考量在 Azure App Service 上執行 Web 應用程式。

下列範例示範如何設定不含其他繫結或輸出的 HTTP 觸發函式。 在此範例中實作的案例包含名為 hello 的函式,其的接受 GETPOST

實作

在名為 hello 的資料夾中,function.json 檔案會設定 HTTP 觸發的函式。

hello/function.json

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

函式會設定為接受 GETPOST 要求,而且結果值是透過名為 res 的引數提供。

在應用程式的根目錄中,host.json 檔案會設定為執行 handler.exe,且 enableForwardingHttpRequest 會設定為 true

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    },
    "enableForwardingHttpRequest": true
  }
}

enableForwardingHttpRequesttrue 時,僅限 HTTP 函式的行為與預設自訂處理常式的行為在下列方面有所不同:

  • HTTP 要求不包含自訂處理常式的要求承載。 相反地,Functions 主機會使用原始 HTTP 要求的複本叫用處理常式。
  • Functions 主機會使用與原始要求相同的路徑叫用處理常式,包括任何查詢字串參數。
  • Functions 主機會傳回處理常式的 HTTP 回應複本,作為原始要求的回應。

以下是對 Functions 主機的 POST 要求。 然後,Functions 主機會將要求的複本傳送至相同路徑的自訂處理常式。

POST http://127.0.0.1:7071/api/hello HTTP/1.1
Content-Type: application/json

{
  "message": "Hello World!"
}

handler.go 檔案會實作網頁伺服器和 HTTP 函式。

package main

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

func helloHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	if r.Method == "GET" {
		w.Write([]byte("hello world"))
	} else {
		body, _ := ioutil.ReadAll(r.Body)
		w.Write(body)
	}
}

func main() {
	customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
	if !exists {
		customHandlerPort = "8080"
	}
	mux := http.NewServeMux()
	mux.HandleFunc("/api/hello", helloHandler)
	fmt.Println("Go server Listening on: ", customHandlerPort)
	log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
}

在此範例中,自訂處理常式會建立網頁伺服器來處理 HTTP 事件,並設定為透過 FUNCTIONS_CUSTOMHANDLER_PORT 接聽要求。

GET 要求會藉由傳回字串來處理,而 POST 要求可以存取要求本文。

這裡的訂單函式路由為 /api/hello,與原始要求相同。

注意

FUNCTIONS_CUSTOMHANDLER_PORT 不是用來呼叫函式對外公開的連接埠。 Functions 主機會使用此連接埠來呼叫自訂處理常式。

部署

自訂處理常式可以部署到每個 Azure Functions 裝載選項。 如果您的處理常式需要作業系統或平台相依性 (例如語言執行平台),您可能需要使用自訂容器

在 Azure 中為自訂處理常式建立函式應用程式時,建議您選取 .NET Core 作為堆疊。

若要使用 Azure Functions Core Tools 來部署自訂處理常式應用程式,請執行下列命令。

func azure functionapp publish $functionAppName

注意

確保執行您的自訂處理常式所需的所有檔案都位於資料夾中,並包含在部署中。 如果您的自訂處理常式是二進位可執行檔,或具有平台特定的相依性,請確保這些檔案符合目標部署平台。

限制

  • 自訂處理常式網頁伺服器必須在 60 秒內啟動。

範例

如需如何以各種不同語言實作函式的範例,請參閱自訂處理常式範例 GitHub 存放庫

疑難排解及支援

追蹤記錄

如果您的自訂處理常式程序無法啟動,或如果它與 Functions 主機通訊發生問題,您可以將函式應用程式的記錄層級增加為 Trace,以查看來自主機的更多診斷訊息。

若要變更函式應用程式的預設記錄層級,請在 host.jsonlogging 區段中設定 logLevel 設定。

{
  "version": "2.0",
  "customHandler": {
    "description": {
      "defaultExecutablePath": "handler.exe"
    }
  },
  "logging": {
    "logLevel": {
      "default": "Trace"
    }
  }
}

Functions 主機會輸出額外的記錄訊息,包括與自訂處理常式程序相關的資訊。 使用記錄來調查啟動自訂處理常式程序或在自訂處理常式中叫用函式的問題。

在本機,記錄會列印到主控台。

在 Azure 中,查詢 Application Insights 追蹤以檢視記錄訊息。 如果您的應用程式會產生大量記錄,則只會將記錄訊息的子集傳送至 Application Insights。 停用取樣以確保會記錄所有訊息。

隔離測試自訂處理常式

自訂處理常式應用程式是網頁伺服器程序,因此使用 cURLPostman 之類的工具來傳送模擬 HTTP 要求,以自行啟動它並測試函式叫用可能會很有幫助。

您也可以在 CI/CD 管線中使用此策略,以在自訂處理常式上執行自動化測試。

執行環境

自訂處理常式會在與一般 Azure Functions 應用程式相同的環境中執行。 測試您的處理常式,以確保環境包含它執行所需的所有相依性。 對於需要額外相依性的應用程式,您可能需要使用裝載在 Azure Functions 進階方案自訂容器映像來執行它們。

取得支援

如果您需要使用自訂處理常式的函式應用程式的協助,您可以透過一般支援管道提交要求。 不過,由於用來建置自訂處理常式應用程式的廣泛語言,支援並非無限制提供。

如果 Functions 主機發生啟動或與自訂處理常式通訊的問題,則會提供支援。 針對自訂處理常式程序內部工作的特定問題,例如所選語言或架構的問題,我們的支援小組無法在此範圍提供協助。

下一步

使用自訂處理常式快速入門,開始在 Go 或 Rust 中建置 Azure Functions 應用程式。