* 기본 개념
- HTML
: Hyper Text Markup Language
=> 하이퍼 텍스트(하이퍼링크를 가진 텍스트)를 만드는 언어
- HTTP
: Hyper text treansfer protocol
=> 하이퍼 텍스트를 전송하는 약속 ( 요청 / 응답을 어떻게 줄것인지)
>과정
- 클라이언트(웹브라우저) http://www.naver.com -> DNS(Domain Name Server) - 도메인에 해당하는 아이피주소를 가짐(아이피알려주는 역할) : 클라이언트에서 네이버 아이피주소를 요청하여 DNS로부터 응답받음
- 클라이언트 -> 네이버 아이피주소에 요청(Request) / 요청을 어떻게 날릴것인지는 HTTP에 정의되어 있다.
- 네이버 서버에서 요청을 받으면 HTML텍스트를 만들어서 응답(response) / 이것도 HTTP에 정의됨
- 응답 받으면 웹브라우저는 자신의 브라우저에 렌더링함
- 과거 : 문서를 요청하고 문서를 받는 한 번만 요청하면 끝나는 경우가 많았다.
- => 서버 렌더링 시대 (요청 보내면 HTML 만들어서 반응)
- 현재 : 상호작용이 중요해지고 동적 웹이 잘 되어있다.
- => 클라이언트 렌더링 시대 ( 요청 보내면 HTML은 틀을 response하면 렌더링할 때 동적인 것들을 틀에 채워 넣음 예를 들면 JSON 데이터)
* 웹 서버 만들기
package main
import (
"fmt"
"net/http"
)
//핸들러 만들기
type fooHandler struct{}
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello Foo!")
}
// 웹 서버 만들기
func main() {
// 정적인 핸들러 등록
// 기본 경로 핸들러 함수
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
// 지정 경로 핸들러 함수
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello Bar!")
})
//직접 만든 핸들러 함수 사용
http.Handle("/foo", &fooHandler{})
http.ListenAndServe(":3000", nil)
}
=> 크롬에 http:l//ocalhost:3000 접속하면 Hello World가 뜬다. -> 서버가 만들어짐
=> 경로가 뒤에 "/bar"라면 그에 해당하는 핸들러 함수가 실행됨
// 기본 경로 핸들러 함수
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
- HandleFunc() : HTTP 핸들러 함수로 웹 서버에서 특정 URL에 대한 요청을 처리한다.
- "/" : 절대경로(초기경로, 인덱스 경로) 즉 도메인의 첫번째 경로 - www.naver.com\
- 첫 번째 경로에 대한 요청이 왔을 때 func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")} 이 함수를 실행- w http.ResponseWriter : HTTP 응답을 작성하는 데 사용하는 인자 w
- r *http.Request : 클라이언트의 HTTP 요청에 대한 정보를 포함하는 구조체로 요청 메서드, 헤더, URL 등의 정보를 제공
- go언어에서 *은 포인터를 나타내며 포인터는 메모리 주소를 가리킴 (여기서는 http.Request 구조체의 인스턴스를 가리키는 포인터)
- fmt.Fprint() : 이 함수의 목적은 지정된 출력 스트림(예: 파일, 네트워크 연결, HTTP 응답 등)에 값을 쓰는 것
- fmt.Fprint(w, "Hello World") : w에 지정된 출력 스트림에 "Hello World" 문자열을 쓰는 것
// 지정 경로 핸들러 함수
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request){
fmt.Fprint(w, "Hello Bar!")
})
- 위와 동일한 핸들러 함수를 사용하였고 인자로 받는 경로가 절대경로가 아닌 bar가 붙는 경로로 지정
//핸들러 정의
type fooHandler struct{}
//함수 구현
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello Foo!")
}
//핸들러 등록
func main() {
//직접 만든 핸들러 함수 사용
http.Handle("/foo", &fooHandler{})
}
- 인스턴스형으로 등록하는 핸들러(다른 핸들러의 경우 함수형으로 등록하는 핸들러임)
- type fooHandler struct{} : 사용자 정의 핸들러의 타입을 나타내는 구조체로 이 구조체는 아무 필드도 가지고 있지 않음
- fooHandler 구조체에 ServeHTTP 메서드를 추가
- go에서 핸들러 인터페이스의 형태 : type Handler interface { ServeHTTP(ResponseWriter, *Request) }
- fooHandler에서 ServeHTTP를 함수에서 구현하고 있음 - HTTP 요청이 들어올 때 이를 처리하고 응답을 생성하는 역할
- 메인 함수에 핸들러 등록
- http.Handle 함수를 사용하여 경로(/foo)와 핸들러(&fooHandler{})를 연결
- &footHandler{}
- & 기호는 Go 언어에서 주소를 나타내는 연산자
- fooHandler{}는 fooHandler 구조체의 값이고, &fooHandler{}는 fooHandler 구조체의 포인터
http.ListenAndServe(":3000", nil)
- http.ListenAndServe() : 웹 서버 실행 함수로 요청을 기다림
- ":3000" : 기다리는 포트는 3000번 포트
- nil : 핸들러를 지정하지 않았기에 기본 핸들러를 사용
- 핸들러 지정할 경우 http.ListenAndServe(":3000", http.DefaultServeMux) 이런식으로 코드 작성
✔️mux
- Go에서 가장 흔히 사용되는 라우터 중 하나로 net/http 패키지에 내장된 http.ServeMux가 있다. 이 라우터는 경로에 따라 핸들러를 매핑하는데 사용한다.
- 라우터 : 어떤 경로로 들어온 요청에 대해 어떤 처리를 해야 하는지 결정하는 메커니즘(HTTP 요청을 받아서, 어떤 핸들러 함수나 처리 로직으로 연결할지를 결정하는 역할을 한다.)
package main
import (
"fmt"
"net/http"
)
func main() {
// ServeMux 생성
mux := http.NewServeMux()
// 경로와 핸들러 함수 매핑
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
})
mux.HandleFunc("/greet", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Greetings!")
})
mux.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "User Page")
})
// 서버 시작
http.ListenAndServe(":3000", mux)
}
=> 이렇게 mux를 사용할 수도 있다.
<Query String 파라미터 - GET>
- 웹 브라우저에서 서버로 Request를 보낼 때 Input값을 보내는 경우
package main
import (
"fmt"
"net/http"
)
//핸들러 함수 정의
func barHandler(w http.ResponseWriter, r *http.Request) {
//동적 URL에서 name이라는 변수 추출
name := r.URL.Query().Get("name")
if name == ""{
name = "World"
}
//출력
fmt.Fprintf(w, "Hello %s!", name)
}
// 웹 서버 만들기
func main() {
//라우터 생성
mux := http.NewServeMux()
mux.HandleFunc("/bar", barHandler)
http.ListenAndServe(":3000", mux)
}
- name := r.URL.Query().Get("name")
- 웹 브라우저에서 서버로 Request를 보낼 때 Input값을 함께 보낼 수 있음
- r은 http.Request 타입의 객체
- URL은 요청된 URL을 나타내는 속성
- Query() 메서드는 URL에서 쿼리 매개변수를 추출하는 역할
- Get("name") 부분은 "name"이라는 쿼리 매개변수의 값을 가져오는 역할
- fmt.Fprint(w, "Hello %s", name)
- %s는 포맷 문자열에서 문자열(string)을 삽입하는 데 사용되는 플레이스홀더로 이는 주로 문자열을 다른 문자열에 삽입하거나, 문자열을 특정한 형식으로 포맷팅할 때 사용한다.
- 포맷팅(Formatting) : 특정한 규칙에 따라 데이터를 가공하거나 출력을 꾸미는 과정
=> name에 짱구를 넣었을 때
* 데이터 형식 - JSON
- XML(Extensible Markup Language) : 데이터를 표현하고 교환하기 위한 마크업 언어
- JSON(JavaScript Object Notation) : 데이터 교환을 위해 사용되는 경량의 데이터 형식으로 사람과 기계 모두 이해하기 쉬운 텍스트 기반의 데이터 포맷이다.
- 데이터를 표현하는 데에 계층 구조를 사용하며, 키-값 쌍으로 이루어진다.
- 다양한 자료형을 지원하며, 객체(Object), 배열(Array), 문자열(String), 숫자(Number), 불리언(Boolean), null 등을 표현할 수 있다.
- XML보다 JSON형식이 더 간결하고 가독성이 좋아서 많이 사용됨
- 객체(Object): 중괄호 {}로 표현되며, 키와 값의 쌍으로 이루어진다. 각 쌍은 콤마 ,로 구분
{
"name": "John",
"age": 30,
"city": "New York"
}
- 배열(Array): 대괄호 []로 표현되며, 값들의 리스트이다. 각 값은 콤마 ,로 구분
["apple", "banana", "orange"]
- 문자열(String): 큰따옴표 ""로 둘러싸인 문자열
"Hello, World!"
- 숫자(Number): 정수 또는 부동소수점 숫자
- 불리언(Boolean): true 또는 false
- null: 아무 값이 없음
<Body 파라미터 - POST>
- 웹 브라우저에서 서버로 Request를 보낼 때 Json 데이터 값을 보내고 다시 Json 데이터를 받아오는 경우
//Json 담을 struct생성
type User struct {
FirstName string
LastName string
Email string
CreateAt time.Time
}
// 핸들러 만들기
type fooHandler struct{}
func (f *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// User 구조체의 인스턴스 생성
user := new(User)
// HTTP 요청의 바디(body)에서 JSON 데이터를 읽어서 User 구조체로 파싱
err := json.NewDecoder(r.Body).Decode(user)
if err != nil {
// 만약 파싱에 실패하면 클라이언트에게 Bad Request 응답을 보냄
w.WriteHeader(http.StatusBadRequest)
fmt.Fprint(w, "Bad Request: ", err)
return
}
// User 구조체에 CreateAt 필드에 현재 시간을 할당
user.CreateAt = time.Now()
// User 구조체를 다시 JSON 형태로 변환
data, _ := json.Marshal(user)
//콘텐츠 타입 명시
w.Header().Add("content-type", "application/json")
// 성공적인 응답을 클라이언트에게 보냄
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(data))
}
- type User struct{}
- 데이터를 담는 구조체
- time.Time : Go 언어에서 시간을 나타내는 데이터 타입
- err := json.NewDecoder(r.Body).Decode(user)
- HTTP 요청의 본문(body)에서 JSON 데이터를 읽어서 Go 언어의 구조체인 User 타입으로 디코딩(이를 통해 클라이언트가 보낸 JSON 데이터를 Go의 데이터 구조로 변환)
- err := : 파싱 실패 시 Bad Request 보내기 위해 - 조건문에서 사용
- 파싱(Parsing) : 주로 컴퓨터 프로그램이 특정한 형식으로 표현된 데이터를 읽어서 내부적으로 사용할 수 있는 구조로 변환하는 과정
- json.NewDecoder( r.Body ) : r.Body로부터 JSON 데이터를 읽기 위한 디코더를 생성
- 디코딩(Decoding) : 주로 데이터의 변환 과정에서 인코딩된 데이터를 원래의 형태나 포맷으로 되돌리는 과정
- .Decode(user): 디코더를 사용하여 JSON 데이터를 읽어와서 user라는 구조체에 매핑 (이 때, JSON 데이터의 각 필드는 user 구조체의 필드에 대응된다.)
- if err != nil { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, "Bad Request: ", err) return }
- if err != nil : 에러가 발생했을 때
- w.WriteHeader(http.StatusBadRequest) : HTTP 상태코드 400번으로 설정
- WriteHeader() : HTTP 응답의 상태 코드를 설정하는 역할
- http.StatusBadRequest : net/http 패키지에 미리 정의된 상수로서, 정확히는 HTTP 상태 코드 400을 나타냄 HTTP 상태 코드 400은 "Bad Request"를 의미
- fmt.Fprint(w, "Bad Request: ", err)
- w에 err메세지 작성(400번 코드)
- return: 에러 처리가 완료되면 함수를 즉시 종료
- user.CreateAt = time.Now()
- user 구조체의 필드인 CreateAt에 현재 시간을 할당한다. (데이터에 생성 시간을 기록하거나 갱신 시간을 업데이트하는 용도)
- data, _ := json.Marshal(user)
- data, _ := : JSON 형식의 데이터를 data 변수에 할당
- '_'는 무시된 값(에러)을 나타내는데, 여기서는 에러를 무시하고자 하는 것(무시하지 않는다면 data, err := 로 쓰면 됨
- json.Marshal(user) : json 패키지에서 제공하는 Marshal 함수를 사용하여 Go의 데이터를 JSON 형식으로 직렬화(마샬링)
- 직렬화(Serialization): 데이터를 특정한 형식으로 변환하여 파일에 저장하거나 네트워크를 통해 전송하고, 나중에 다시 읽거나 사용할 수 있도록 하는 프로세스를 의미
- data, _ := : JSON 형식의 데이터를 data 변수에 할당
- w.Header().Add("content-type", "application/json")
- response가 json형태로 잘 나오도록 만들어줌
<썬더 클라이언트 앱>
=> content-type이 바뀜 => response가 json형태로 잘 나옴
- w.WriteHeader(http.StatusOK)
- HTTP 상태코드 200번으로 설정 - 클라이언트의 요청이 성공적으로 처리됨
- fmt.Fprint(w, string(data))
- string(data)로 변환된 문자열을 http.ResponseWriter에 출력
+) HTTP 클라이언트 도구 - 썬더 클라이언트 설치 (vscode에서 사용 가능)
- 메소드 : POST
- URL : http://localhost:3000/foo
=> Body에 어떤 데이터도 없는 경우에는 400번 Bad Request가 뜬다
=> Body에 데이터를 입력
=> String형태의 데이터로 Response
'개발 공부 > Go' 카테고리의 다른 글
[Go] - CRUD API 구현 (0) | 2023.12.30 |
---|