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 형태 출력

 

p.s 와 이거 하다가 계속 404 not found 뱉어내서 왜지... 왜지 이러고 찾다가 도저히 못찾겠어서 챗GPT한테 물어보니까,

struct에 Json 형태 작성한거에 오타있는거 찾아주고, 테스트 코드 경로 오타(대소문자)낸거 찾아주네...

이래서 다들 챗GPT 쓰는구나........ 진심 너무 소름돋는다..........