Back in 2017 I wrote a post about how to run a precompiled .NET Core Azure Function in a container. Fast forward to 2023 and, as some of you know, I’ve been playing with Golang for a while now so I thought it was about time to translate the .NET code and make it work with Golang.
Prerequisites:
Create an Azure Function with a custom worker runtime
Create an Azure Function with a custom worker runtime
1mkdir dni
2cd dni
3func init --worker-runtime custom
Edit the host.json file
Modify the defaultExecutablePath
to the name of the executable file you want to run. In this case: handler. Also, set the enableForwardingHttpRequest
to true
so the function can access the HTTP request:
1{
2 "version": "2.0",
3 "logging": {
4 "applicationInsights": {
5 "samplingSettings": {
6 "isEnabled": true,
7 "excludedTypes": "Request"
8 }
9 }
10 },
11 "extensionBundle": {
12 "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 "version": "[2.*, 3.0.0)"
14 },
15 "customHandler": {
16 "description": {
17 "defaultExecutablePath": "handlers",
18 "workingDirectory": "",
19 "arguments": []
20 },
21 "enableForwardingHttpRequest": true
22 }
23}
Create a new HTTP Trigger Azure Function
Create a new HTTP Trigger Azure Function
1func new -n dni -t httptrigger
Create a golang handler for the function.
Create a handlers.go
file with the following contents:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7 "net/http"
8 "os"
9 "strconv"
10 "strings"
11)
12
13type InvokeRequest struct {
14 Data map[string]json.RawMessage
15 Metadata map[string]interface{}
16}
17
18type InvokeResponse struct {
19 Outputs map[string]interface{}
20 Logs []string
21 ReturnValue interface{}
22}
23
24func dniHandler(w http.ResponseWriter, r *http.Request) {
25 ua := r.Header.Get("User-Agent")
26 fmt.Printf("user agent is: %s \n", ua)
27 invocationid := r.Header.Get("X-Azure-Functions-InvocationId")
28 fmt.Printf("invocationid is: %s \n", invocationid)
29
30 queryParams := r.URL.Query()
31
32 if dni := queryParams["dni"]; dni != nil {
33 valid := validateDNI(dni[0])
34 js, err := json.Marshal(valid)
35 if err != nil {
36 http.Error(w, err.Error(), http.StatusInternalServerError)
37 return
38 }
39
40 w.Header().Set("Content-Type", "application/json")
41 w.Write(js)
42 } else {
43 http.Error(w, "dni query parameter not present", http.StatusInternalServerError)
44 }
45}
46
47func validateDNI(dni string) bool {
48 table := "TRWAGMYFPDXBNJZSQVHLCKE"
49 foreignerDigits := map[string]string{
50 "X": "0",
51 "Y": "1",
52 "Z": "2",
53 }
54 parsedDNI := strings.ToUpper(dni)
55 if len(parsedDNI) == 9 {
56 checkDigit := parsedDNI[8]
57 parsedDNI = parsedDNI[:8]
58 if foreignerDigits[strings.ToUpper(string(parsedDNI[0]))] != "" {
59 parsedDNI = strings.Replace(parsedDNI, string(parsedDNI[0]), foreignerDigits[string(parsedDNI[0])], 1)
60 }
61 dniNumbers, err := strconv.Atoi(parsedDNI)
62 if err != nil {
63 fmt.Println("Error during conversion")
64 }
65
66 return table[dniNumbers%23] == checkDigit
67 }
68 return false
69}
70
71func main() {
72 customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
73 if !exists {
74 customHandlerPort = "8080"
75 }
76 mux := http.NewServeMux()
77 mux.HandleFunc("/api/dni", dniHandler)
78 fmt.Println("Go server Listening on: ", customHandlerPort)
79 log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
80}
Note: THe code intends to validate a Spanish DNI (Documento Nacional de Identidad) number. It gets the DNI number from the query string and returns a boolean value indicating if the DNI number is valid or not.
Build the executable:
1go build ./handlers.go
Test the Azure Function locally
To test the Azure Function locally run the following command:
1func start --verbose
and from another terminal window run the following command:
1curl -i http://localhost:7071/api/dni?dni=59658914L
You should get a response similar to the following:
1HTTP/1.1 200 OK
2Content-Length: 4
3Content-Type: application/json
4Date: Sun, 26 Mar 2023 11:01:24 GMT
5Server: Kestrel
6
7true
Download all code and files here.
Hope it helps!
Comments