[Golang] Try handling web requests
Tags: go
Published: 2022-05-07 05:37:09

## Intro
This time, I will try handling web requests.
I will use the project what I created last time.

* [[Golang] Try WebSocket](https://dev.to/masanori_msl/golang-try-websocket-1ghm)

## Handling web requests
First I will try handling web requests.
Because I have never tried accessing database in Golang, I will try GET and POST methods in this time.

### Separate operations by HTTP methods
Because "http.Handle" and "http.HandleFunc" don't have functions to specify the HTTP methods, I can't add same routes for each HTTP methods.

#### main.go
```go
package main

import (
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"sync"
)
...
func main() {
...
	http.HandleFunc("/webrequest", webRequestHandler)
	// panic: http: multiple registrations for /webrequest
	http.HandleFunc("/webrequest", func(_ http.ResponseWriter, _ *http.Request) {
		log.Println("webrequest2")
	})
...
}
```

Therefore, I need to route requests for each HTTP method myself as follows.

#### main.go
```go
...
func main() {
...
	http.HandleFunc("/webrequest", webRequestHandler)
...
}
```

#### webRequestSample.go
```go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

func webRequestHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		// For GET request
		break
	case http.MethodPost:
		// For POST request
		break
	}
}
```

### Get query paramters
I can get query paramters from "http.Request".

#### webRequestSample.go
```go
...
func webRequestHandler(w http.ResponseWriter, r *http.Request) {
	name, err := getParam(r, "name")
	if err == nil {
		log.Println(name)
	} else {
		log.Println(err)
	}
...
}
func getParam(r *http.Request, key string) (string, error) {
	result := r.URL.Query().Get(key)
	if len(result) <= 0 {
		return "", errors.New(fmt.Sprintf("no value: %s", key))
	}
	return result, nil
}
```

* [url package - net/url - pkg.go.dev](https://pkg.go.dev/net/url#example-URL.Query)
* [net/http package get Query Params in Go(Golang) - Welcome to Golang by Example](https://golangbyexample.com/net-http-package-get-query-params-golang/)

### Return formatted string values
#### webRequestSample.go
```go
...
func webRequestHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		name, err := getParam(r, "name")
		if err != nil {
			fmt.Fprint(w, "The parameters have no names")
			return
		}
		fmt.Fprintf(w, "Hi there, I love %s!", name)
		break
...
	}
}
...
```

* [Writing Web Applications - The Go Programming Language](https://go.dev/doc/articles/wiki/)

### Return JSON values(+ reading values from body)
#### webRequest.page.ts
```ts
export function send() {
    fetch(targetUrl, 
        {
            method: "POST",
            body: JSON.stringify({ messageType: "text", data: "Hello" }),
        })
        .then(res => res.json())
        .then(json => console.log(json))
        .catch(err => console.error(err));
}
```
#### webRequestSample.go
```go
...
type sampleResult struct {
	Message      string `json:"message"`
	Succeeded    bool   `json:"succeeded"`
	ErrorMessage string `json:"errorMessage"`
}

func webRequestHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
...
	case http.MethodPost:
		returnValue := &sampleResult{}
		w.Header().Set("Content-Type", "application/json")
		// read values from HTTP request body
		body, readBodyError := ioutil.ReadAll(r.Body)

		if readBodyError != nil {
			returnValue.Succeeded = false
			returnValue.ErrorMessage = "Failed reading values from body"
			failedReadingData, _ := json.Marshal(returnValue)
			w.Write(failedReadingData)
			return
		}
		wsMessage := &websocketMessage{}
		convertError := json.Unmarshal(body, &wsMessage)
		if convertError != nil {
			returnValue.Succeeded = false
			returnValue.ErrorMessage = "Failed converting to WebSocketMessage"
			failedConvertingData, _ := json.Marshal(returnValue)
			w.Write(failedConvertingData)
			return
		}
		w.WriteHeader(200)
		returnValue.Message = fmt.Sprintf("%s_1", wsMessage.Data)
		returnValue.Succeeded = true
		returnValue.name = "hello world"
		data, _ := json.Marshal(returnValue)

		w.Write(data)
		break
	}
}
...
```

#### Result
```
{
    "message": "Hello_1",
    "succeeded": true,
    "errorMessage": ""
}
```

#### structs for json.Marshal & json.Unmarshal
I defined "sampleResult" like below.

```go
type sampleResult struct {
	message      string
	succeeded    bool
	errorMessage string
}
```

Because "json.Marshal" and "json.Unmarshal" ignore private properties, the results always became empty values :P

* [json package - encoding/json - pkg.go.dev](https://pkg.go.dev/encoding/json)
* [Return JSON body in HTTP response in Go (Golang) - Welcome To Golang By Example](https://golangbyexample.com/json-response-body-http-go/?msclkid=41fd385ccd5c11ecbfe4aacfcf4d1c30)

### Return file data
#### webRequestSample.go
```go
...
func webRequestHandler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		name, err := getParam(r, "name")
		if err != nil {
			log.Println(err.Error())
			return
		}
		// Search file from current directory
		cur, _ := os.Getwd()
		file, fileErr := os.Open(fmt.Sprintf("%s/files/%s", cur, name))
		if fileErr != nil {
			log.Println(fileErr.Error())
			w.WriteHeader(404)
			return
		}
		// Get file infomations
		fileinfo, staterr := file.Stat()
		if staterr != nil {
			log.Println(staterr.Error())
			w.WriteHeader(404)
			return
		}
		// To download file, I set "application/octet-stream" as Content-Type
		w.Header().Set("Content-Type", "application/octet-stream")
		// set its file name
		cd := mime.FormatMediaType("attachment", map[string]string{"filename": fileinfo.Name()})
		w.Header().Set("Content-Disposition", cd)
		http.ServeContent(w, r, fileinfo.Name(), fileinfo.ModTime(), file)
		break
...
```

#### File name
First, I tried reading the file and return byte array like below.

```go
...
		// Get file infomations
		fileinfo, staterr := file.Stat()
		if staterr != nil {
			log.Println(staterr.Error())
			w.WriteHeader(404)
			return
		}
		// Read file
		fileData := make([]byte, fileinfo.Size())
		fileSize, loadErr := file.Read(fileData)
		if loadErr != nil {
			log.Println(loadErr.Error())
			w.WriteHeader(404)
			return
		}
		// To download file, I set "application/octet-stream" as Content-Type
		w.Header().Set("Content-Type", "application/octet-stream")
		w.Write(fileData)
		break
...
```

But the downloaded files were always named like "webrequest", so I changed to use "http.ServeContent".

* [http package - net/http - pkg.go.dev](https://pkg.go.dev/net/http)
* [How to download file in browser from Go server - StackOverflow](https://stackoverflow.com/questions/24116147/how-to-download-file-in-browser-from-go-server?msclkid=15b0b264cdbe11ec9056762e6fdb103c)
* [Get File Name, Size, Permission Bits, Mode, Modified Time in Go (Golang) - Welcome To Golang By Example](https://golangbyexample.com/file-info-golang/?msclkid=a4ec1f01cdb911ec8afcc05099ef803d)