Language/Golang

Golang (Go언어) Go로 만드는 웹 (2)

HeeWorld 2025. 1. 3. 23:08

Tucker의 Go 언어 프로그래밍 책과 유튜브를 통해 학습 중입니다.

Golang 마스코트 Gopher(고퍼)

 

HTTP 동작 원리

웹 브라우저에 도메인(ex. https://www.google.com) 을 입력한 뒤  엔터를 누르면,

웹 브라우저는 도메인 네임 시스템(DNS)에 해당 도메인에 해당하는 IP 주소를 요청함.

 

만약, IP주소의 목적지가 컴퓨터라면 포트 번호는 수신한 데이터를 놓을 컴퓨터 내 창구와 같음.

IP주소는 컴퓨터 자체를, 포트번호는 해당 컴퓨터 내 데이터를 수신할 수 있는 창구를 의미함.

* 컴퓨터 포트는 0 ~ 65535번 포트를 가지고 있음.

 

포트 없이 https://www.google.com 을 입력하면, 기본 포트 번호로 요청을 전송함.

- http는 80, https는 443

즉, https://www.google.com은 https://www.google.com:443 으로,

http://www.google.com은 http://www.google.com:80으로 요청을 보냄. 

 

https://는 데이터를 보내는 통신 규약으로 HTTPS를 사용하겠다는 것을 나타냄.

(HTTPS는 HTTP에 보안 기능을 추가한 통신 규약)

HTTP는 하이퍼텍스트 전송 규약(HyperText Transfer Protocol)의 약자로 하이퍼텍스트를 전송하는 통신 규약임.

 

* 하이퍼 텍스트란, 

- 하이퍼링크를 포함한 멀티미디어 텍스트로 문자뿐 아니라 그림, 이미지 등의 멀티미디어를 포함하고 다른 문서로 연결되는 링크를 제공하는 문서 포맷

 

웹에서 하이퍼텍스트 문서를 사용하기 때문에 문자, 이미지, 음악, 동영상 등을 볼 수 있고 링크를 통해 다른 페이지로 연결할 수 있음.

하이퍼텍스트 문서를 만들 수 있는 문서 포맷이 하이퍼텍스트 마크업 언어(HyperText Markup Language)의 약자인 HTML 포맷임.

 

웹서버란, 특정 포트에서 대기하며 사용자의 HTTP 요청에 HTTP 응답을 전송하는 서버를 말함.

일반적으로 HTML 문서를 전송함.

 

 

mux 사용하기

- "/", "/bar" 등의 경로에 따라 다르게 분배하는 것을 라우터/mux라고 부름.

- 멀티플렉서(Multiplexer)의 약자로 여러 입력 중 하나를 선택해서 반환하는 디지털 장치를 의미.

- 기존과 다른 점은 mux라는 인스턴스를 만들어서 거기에 등록하여 인스턴스를 넘겨주는 방식으로 바꿈.

 

* 웹 서버는 각 URL에 해당하는 핸들러들을 등록한 다음 HTTP 요청이 왔을 때 URL에 해당하는 핸들러를 선택해서 실행하는 방식.

* 핸들러를 선택하고 실행하는 구조체 이름이 Mux를 제공한다고 하여, ServeMux라고 부름.

package main

import (
	"fmt"
	"net/http"
)

func barHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello Bar!")
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Hello World")
	})

	mux.HandleFunc("/bar", barHandler)

	http.ListenAndServe(":3000", mux)
}

 

→ 결과는 동일하게 웹에서 확인할 수 있음!

 

기본 포트 호출
/bar 경로 호출

 

func barHandler(w http.ResponseWriter, r *http.Request) {
	name := r.URL.Query().Get("name")
	if name == "" {
		name = "World"
	}
	fmt.Fprintf(w, "Hello %s!", name)
}

 

- URL에서 name이라는 아큐먼트를 뽑아내서 그 값을 Hello Name이 출력되게하고,

   만약 Name이 없다면, Hello World를 출력되게 만드는 코드

 

- URL 뒤에 ?를 붙여 쿼리 인수가 시작됨을 표시하고, 각 인수는 key=value 형태로 입력함.

   ex) http://localhost:3000/bar?name=<value>

   2개 이상의 인수를 사용할 때는 &를 사용해 인수들을 연결함.



→ 출력 결과를 확인해보면, 아래와 같이 name 값을 가지고 출력하는 것을 확인할 수 있음.

     

name=hee
name=red

 

JSon 데이터 전송

- JSON은 자바스크립트 오브젝트 표기법(JavaScript Object Notaion)의 약자로 자바스크립트에서 오브젝트를 표현하는 방법으로 사용되는 포맷

 

* JSON 표기 규칙

- 오브젝트 시작은 {로 표기하고 }로 종료

- 각 필드는 "key":value 형태로 표기

- 각 필드는 ,로 구분

- 배열은 []로 표기

- 문자열은 ""로 묶어서 표기

{
  "Name":"훈이",
  "Age":16,
  "Score":90
}

 

type User struct {
	FirstName string
	LastName  string
	Email     string
	CreatedAt time.Time
}

type fooHandler struct{}

// ServeHTTP implements http.Handler.
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	user := new(User)
	err := json.NewDecoder(r.Body).Decode(user)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		fmt.Fprint(w, "Bad Requset: ", err)
		return
	}
	user.CreatedAt = time.Now()

	data, _ := json.Marshal(user)
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, string(data))
}

 

- Json 데이터를 담을 struct인 User struct를 새로 만들어줌.

 

- *fooHandler의 Request에 Json이 오기 때문에 그것을 읽어야해서 (user struct의 값) 값을 채워줄 인스턴스를 생성 

- 해당 값을 Json 형태로 파싱을 해야해서, json형태의 NewDecoder로 Request의 바디에 있는 Json를 reader로 받음.

   바디에서 데이터를 읽어서 user struct의 형태로 Decode함(decode해서 값을 채워줌).

   user형태의 Json 파일 형태가 아닌 경우에 error를 뱉음.

 

- err가 nil이 아닌 경우 Header로 Bad Request를 보내고, Response로 Bad Request와 error를 뱉어줌.

 

- user의 CreateAt을 현재 시간으로 변경함.

 

- 바뀐 User 값을 다시 Json으로 리턴하기 위해, Marshal을 사용하여 Json 형태로 encoding을 해줌.

 

NewDecoder 설명
body 설명
ReadCloser 설명

 

→ http://localhost:3000/foo를 호출하면 Hello foo!가 아닌 Bad Request EOF(End Of File)이 나오게 됨.

- 데이터가 없다는 의미로, 데이터를 body에 넣어서 보내야 함.

  (강의에서는 크롬 앱인 Advanced REST client를 사용하셨는데, 25/1/3 기준으로 해당 앱은 사용할 수 없다고 나옴.)

foo 호출 시

 

웹서버 생성 코드

package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"time"
)

type User struct {
	FirstName string    `json:"First_name"`
	LastName  string    `json:"Last_name"`
	Email     string    `json:"email"`
	CreatedAt time.Time `json:"Created_at"`
}

func MakeWebHandler() http.Handler {	// 핸들러 인스턴스를 생성하는 함수
	mux := http.NewServeMux()
	mux.HandleFunc("/user", UserHandler)
	return mux
}

func UserHandler(w http.ResponseWriter, r *http.Request) {
	var user = User{"Hello", "World", "helloworld@gmail.com", time.Time{}}
	data, _ := json.Marshal(user)	// User 객체를 []byte로 변환
	w.Header().Add("content-type", "application/json")	// Json 포맷임을 표시
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, string(data))	// 결과 전송
}

func main() {
	http.ListenAndServe(":3000", MakeWebHandler())
}

 

테스트 실행 코드

package main

import (
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestJsonHandler(t *testing.T) {
	assert := assert.New(t)

	res := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/user", nil)	// /user 경로 테스트

	mux := MakeWebHandler()
	mux.ServeHTTP(res, req)

	assert.Equal(http.StatusOK, res.Code)
	user := new(User)
	err := json.NewDecoder(res.Body).Decode(user)	// 결과 반환
	assert.Nil(err)	// 결과 확인
	assert.Equal("Hello", user.FirstName)
	assert.Equal("World", user.LastName)
	assert.Equal("helloworld@gmail.com", user.Email)
	assert.Equal(time.Time{}, user.CreatedAt)
}

 

Json 형태 출력