GO http 4일차 - HTTP Header 설정, Not found, Redirect 사용

0

GO http

목록 보기
4/5

HTTP Header와 response status code 전송

클라이언트가 만약 잘못된 http method로 요청을 보냈다면 어떻게 해야할까? 또는 잘못된 정보나 인증되지 않은 접근을 할 때 어떻게 해야할까?? HTTP Hedaer와 본문에 필요한 정보를 붙여서 전달해주어야 한다. 가령 Header안에 가능한 method들이 무엇인지 적어주고, status code도 전송해주면 된다.

golang에서는 http.ResponseWriter를 통해서 Header를 작성하고 데이터를 전달할 수 있다. 이때 Header().Set()Header().Add()가 있는데, Setkey-value형식은 haader정보를 초기화하여 아예 새로운 정보를 넣는 것이고, Add는 기존의 header정보를 놔두고 새로운 정보를 덧붙이는 기능이다.

POST http method 이외에 다른 http method로 요청이 오게 될 때 405( Method Not Allowed ) 메시지를 전송하도록 하자.

package main

import "net/http"

func goHandler(res http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case "POST":
	default:
		res.Header().Set("Allow", http.MethodPost)
		res.WriteHeader(405)
		res.Write([]byte("Method Not Allowed"))
	}
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/go", goHandler)
	http.ListenAndServe(":8888", mux)
}

요청을 보내어 결과를 확인해보도록 하자.

curl localhost:8888/go -v

다음의 결과가 반환된다.

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8888 (#0)
> GET /go HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 405 Method Not Allowed
< Allow: POST
< Date: Thu, 27 Oct 2022 01:35:49 GMT
< Content-Length: 18
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact

응답 부분을 잘보면 HTTP/1.1 405 Method Not Allowed라고 써있는 부분이 있다. 순서대로 protocol status-code status-info 순서이다. 우리는 405를 전달하였고, 405Method Not Allowed이기 때문에 반환받은 응답에서 위와 같이 나온 것이다.

기본적으로 status codeHeader()Set해주지 않으면 200이 전달된다. 또한 굳이 status code405와 같이 숫자로 써줄 필요는 없다. http.StatusMethodNotAllowednet/http에서 제공하는 상태코드 값으로 const로 모두 정의되어있다.

const (
    ...
	StatusOK                   = 200 // RFC 7231, 6.3.1
	StatusCreated              = 201 // RFC 7231, 6.3.2
	StatusAccepted             = 202 // RFC 7231, 6.3.3
    ...
	StatusMovedPermanently  = 301 // RFC 7231, 6.4.2
	StatusFound             = 302 // RFC 7231, 6.4.3
	StatusTemporaryRedirect = 307 // RFC 7231, 6.4.7
	StatusPermanentRedirect = 308 // RFC 7538, 3
    ...
	StatusBadRequest                   = 400 // RFC 7231, 6.5.1
	StatusUnauthorized                 = 401 // RFC 7235, 3.1
	StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
	StatusForbidden                    = 403 // RFC 7231, 6.5.3
	StatusNotFound                     = 404 // RFC 7231, 6.5.4
	StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
	StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
	StatusProxyAuthRequired            = 407 // RFC 7235, 3.2
    ...
	StatusInternalServerError           = 500 // RFC 7231, 6.6.1
	StatusNotImplemented                = 501 // RFC 7231, 6.6.2
	StatusBadGateway                    = 502 // RFC 7231, 6.6.3
	StatusServiceUnavailable            = 503 // RFC 7231, 6.6.4
)

다음과 같이 주요 사용되는 status code들이 가독성 좋게 이미 구현되어있으므로 이를 사용하면 된다. 참고로 http method인 GET, POST, PUT, DELETE등도 마찬가지이다.

const (
	MethodGet     = "GET"
	MethodHead    = "HEAD"
	MethodPost    = "POST"
	MethodPut     = "PUT"
	MethodPatch   = "PATCH" // RFC 5789
	MethodDelete  = "DELETE"
	MethodConnect = "CONNECT"
	MethodOptions = "OPTIONS"
	MethodTrace   = "TRACE"
)

POSThttp.MethodPost로 쓸 수 있는 것이다.

추후에 나오겠지만, 이러한 Header를 이용하여 cookie, session 등을 사용할 수 있다.

NotFound 처리하기

클라이언트가 없는 URL로 요청을 보냈을 때, NotFound와 처리를 하고싶다면 어떻게해야할까?? 커스텀하게 만들 수도 있지만, 기본적으로 go에서 제공하는 함수를 사용해보자.

package main

import "net/http"

func helloHandler(res http.ResponseWriter, req *http.Request) {
	res.Write([]byte("hello world"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", helloHandler)
	http.ListenAndServe(":8888", mux)
}

다음의 코드를 보자, url "/"로 들어온 요청은 helloHandler로 빠지게 된다. 그런데 없는 url로 요청을 보내도 "/"로 보내져 helloHandler로 들어오는 것을 확인할 수 있다.

curl localhost:8888/2
curl localhost:8888/23

/2, /3 둘 다 지원하지않는 url임에도 불구하고, 모두 hello world라는 결과값을 받을 수 있을 것이다.

이는 go의 servemux가 두 가지 URL 패턴 타입을 지원하기 때문인데, fixed pathssubtree paths이다. fixed path/로 끝나지 않은 패턴이고, subtree paths/로 끝나는 패턴이다.

fixed path는 고정된 url값에 딱 맞는 것만 매칭시키는 것이다. 가령 /hello라고 url을 작성하면 요청이 /hello로 딱와야지만 된다. 가령 /hellos, /hello/name와 같은 요청을 처리하지 않는다.

반면 subtree paths는 url값에 딱 맞는 것만이 아니라 /아래의 모든 요청을 처리한다. /hello/라고 써있다면 /hello//hello/32 , /hello/32/name 등 모든 요청을 처리한다.

우리는 /로 끝나는 패턴을 사용하였기 때문에 subtree paths를 사용하고 있는 것이며, 이는 wildcard로 /**와 같다. 즉 /로 아래로 fixed path이외의 모든 요청을 다 받겠다는 것이다.

그렇다면 /이외의 모든 요청이 들어올텐데 이를 어떻게 처리하면 될까??

http.Request에 있는 URLpath를 이용하여 원하는 path가 아니면 NotFound로 처리하면 된다.

package main

import "net/http"

func helloHandler(res http.ResponseWriter, req *http.Request) {
	if req.URL.Path != "" {
		res.WriteHeader(http.StatusNotFound)
		res.Write([]byte("Not Found"))
		return
	}
	res.Write([]byte("hello world"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", helloHandler)
	http.ListenAndServe(":8888", mux)
}

다음과 같이 req.URL.Pathurl을 알아낸 다음, WriteHeader로 status code Not Found를 넣어주고 반환하면 된다. 참고로 return을 하지 않으면 종료되지 않기 때문에 "hello world"도 같이 보내게 된다.

추가적으로 go에서 기본적으로 제공하고 있는 http.NotFound() 함수를 이용해도 된다.

// NotFound replies to the request with an HTTP 404 not found error.
func NotFound(w ResponseWriter, r *Request) { Error(w, "404 page not found", StatusNotFound) }

// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return HandlerFunc(NotFound) }

우리는 HandlerFunc으로만 작업을 하고 있으므로, NotFound를 사용하도록 하자. 코드 내부를 보면 자동으로 메시지와 StatusNotFound를 전송하는 것으로 확인된다.

func helloHandler(res http.ResponseWriter, req *http.Request) {
	if req.URL.Path != "" {
		http.NotFound(res, req)
		return
	}
	res.Write([]byte("hello world"))
}

helloHandler를 위와 같이 바꾸고 요청을 보내보도록 하자.

curl localhost:8888/32 -v

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8888 (#0)
> GET /32 HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Content-Type: text/plain; charset=utf-8
< X-Content-Type-Options: nosniff
< Date: Thu, 27 Oct 2022 02:55:42 GMT
< Content-Length: 19
<
404 page not found
* Connection #0 to host localhost left intact

404 page not found가 잘 도착하였고, header의 status code도 Not Found로 잘 온 것을 확인할 수 있다.

Redirect 처리하기

이번에는 redirect를 처리해보도록 하자. 특정 url이 이제 더이상 쓰이지 않는 경우나 다른 url로 변경된 경우에 많이 사용된다. 우리는 위에서 작성했던 /go url이 /golang으로 변경되었다고 가정하자. 어떻게 redirect를 할 수 있을까?

이것도 net/http에서 기본적으로 제공해주는 http.Redirect함수를 사용하면 된다.

package main

import "net/http"

func goHandler(res http.ResponseWriter, req *http.Request) {
	http.Redirect(res, req, "/golang", http.StatusMovedPermanently)
}

func golangHandler(res http.ResponseWriter, req *http.Request) {
	res.Write([]byte("golang go"))
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/go", goHandler)
	mux.HandleFunc("/golang", golangHandler)
	http.ListenAndServe(":8888", mux)
}

다음과 같이 http.Redirect(res, req, "/golang", http.StatusMovedPermanently)을 사용해주면 된다. http.Redirect의 매개변수는 차례대로 resposne, request, url, status code이다. 지정한 url로 redirect가 수행되고, status code를 헤더에 넣어준다.

curl localhost:8888/go -v

*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8888 (#0)
> GET /go HTTP/1.1
> Host: localhost:8888
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-Type: text/html; charset=utf-8
< Location: /golang
< Date: Thu, 27 Oct 2022 04:33:26 GMT
< Content-Length: 42
<
<a href="/golang">Moved Permanently</a>.

* Connection #0 to host localhost left intact

다음과 같은 결과가 나오게 된다. status code도 원하는 대로 301이 잘 써졌으며 <a href="/golang">Moved Permanently</a>. 결과로 리다이렉트 화면으로 넘어갈 수 있도록 할 수 있다.

0개의 댓글