Exercise - Use a custom handler to build an app

Completed

In this exercise, you'll build and run a serverless app by using Go.

Scaffold the app

Begin by scaffolding the app, by using the Azure Functions extension in Visual Studio Code.

  1. Select View > Command Palette.
  2. Select Azure Functions: Create New Project.
  3. Select a folder, usually your current folder.
  4. In Select a language, select Custom Handler.
  5. In Select a template for your first function, select HttpTrigger.
  6. Give the app a name, such as hello.
  7. Select an authorization level of anonymous. You can change that later if you want.

Now you have a project that looks something like this:

hello/
  function.json
.funcignore
.gitignore
host.json
local.settings.json
proxies.json

Create the app

The next series of steps is about creating an app that can respond to an HTTP trigger.

  1. Create a file named server.go at the project root.

  2. Give server.go the following content:

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

    The preceding code imports all the libraries that you need to build an HTTP app and to look up environment variables.

  3. Add the following code after the import statements:

    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))
    }
    

    The main() function is invoked by itself. The first line of the code states how it will read from the FUNCTIONS_CUSTOM_HANDLER_PORT environment variable:

    customHandlerPort, exists := os.LookupEnv("FUNCTIONS_CUSTOMHANDLER_PORT")
    

    Next, the function checks whether the port exists. If not, the function is assigned port 8080:

    if !exists {
      customHandlerPort = "8080"
    }
    

    The next code instantiates an HTTP server instance:

    mux := http.NewServeMux()
    

    The last line of importance is the one that starts listening to a specific port and signals that it's ready to receive requests, with the method ListenAndServe():

    log.Fatal(http.ListenAndServe(":"+customHandlerPort, mux))
    
  4. Let's add the remaining code. First, localize the following line and uncomment it:

    // mux.HandleFunc("/api/hello", helloHandler)
    
  5. Between the import statements and the main() function, add the following code:

    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)
      }
    }
    

    The helloHandler() function sets the content type to application/json. It responds with either "hello world" or the posted body, if any.

Run the app

You're done authoring the code at this point, but you need to do some configuration for this scenario to work. You need to point out where your executable file is, so the Function host can find it. You also need to configure the routing and state that this app deals with HTTP triggers and no other types of bindings.

  1. From a terminal, run go build server.go in the project root:

    go build server.go
    

    This step creates an executable file that's called server on macOS and Linux, or server.exe on a Windows OS.

  2. Open the host.json file and find the defaultExecutablePath element inside the customHandler one. Specify ./server on macOS and Linux, or .\server.exe on a Windows OS.

  3. Under the customHandler element, add the enableForwardingHttpRequest element and give it the value true. Your customHandler element should now look like this:

    "customHandler": {
     "description": {
       "defaultExecutablePath": "./server",
       "workingDirectory": "",
       "arguments": []
     },
     "enableForwardingHttpRequest" : true
    }
    
  4. From a terminal, run func start in the project root. Doing so starts your Functions app.

    func start
    

    At the end of the output, you'll see an output similar to:

    Functions:
    
         hello: [GET,POST] http://localhost:7071/api/hello
    
  5. In a browser, go to http://localhost:7071/api/hello. You should see the output "hello world."

Congratulations! You've developed a serverless app in Go.