package main
import (
"encoding/json" // 데이터를 JSON 형식으로 인코딩/디코딩하기 위한 패키지
"net/http" // HTTP 프로토콜을 이용한 서버/클라이언트 구현 위한 패키지
"sort" // 데이터를 정렬하는 함수를 제공하는 패키지
"strconv" // 문자열을 기본 데이터 타입으로 변환하는 기능 제공
"github.com/gorilla/mux" // 강력한 URL 라우팅 기능을 제공하는 외부 패키지
)
type Student struct { // 학생의 정보를 나타내는 구조체
Id int
Name string
Age int
Score int
}
1) REST API를 구현할 때 주로 사용하는 패키지는 다음과 같다:
net/http: HTTP 서버와 클라이언트 기능을 제공encoding/json: JSON 데이터의 인코딩 및 디코딩을 처리github.com/gorilla/mux: 강력한 URL 라우팅 및 디스패치 기능을 제공, 요청 URL과 관련된 핸들러를 매핑하는 데 사용2) 데이터 모델은 구조체(Struct)를 사용하여 정의
// 전역 변수 students : 학생의 ID를 키로 하여 Student 객체를 저장
var students map[int]Student
// 여기서는 고유 식별자의 역할을 하는 Id 필드가 자연스럽게 키로 사용된다!
var lastId int // 가장 최근에 추가된 마지막 학생의 ID를 추적
// 핸들러는 HTTP 요청을 받아서 그에 대응하는 작업을 수행하고, 결과를 클라이언트에게 돌려주는 역할
func MakeWebHandler() http.Handler { // 웹 서버의 요청을 처리할 라우터를 설정하고 반환
mux := mux.NewRouter() // gorilla/mux 생성 [새로운 라우터 생성]
// client가 특정 URL로 요청을 보낼 때, 해당 URL에 맞는 핸들러 함수를 찾아 실행해주는 것이 라우터의 역할
// 1. 학생 목록 조회하는 핸들러 함수와 경로 등록
// -> "/students" 경로로 "GET" 요청이 들어올 때 GetStudentListHandler 함수가 호출되도록 설정
mux.HandleFunc("/students", GetStudentListHandler).Methods("GET")
//2. 특정 학생 정보 조회
mux.HandleFunc("/students/{id:[0-9]+}", GetStudentHandler).Methods("GET")
// 3. 학생 데이터 추가
mux.HandleFunc("/students", PostStudentHandler).Methods("POST")
// 4. 학생 데이터 삭제
mux.HandleFunc("/students/{id:[0-9]+}", DeleteStudentHandler).Methods("DELETE")
// 초기 학생 데이터 설정
students = make(map[int]Student)
// 임시 학생 데이터 두 개 생성해서 저장
students[1] = Student{1, "aaa", 16, 87}
students[2] = Student{2, "bbb", 18, 98}
lastId = 2
return mux
}
gorilla/mux를 사용하여 다양한 HTTP 경로에 대한 요청을 처리할 핸들러 함수를 등록MakeWebHandler 함수는 HTTP 요청을 처리할 각 경로별 핸들러 함수를 설정하고, 이렇게 설정된 라우터를 반환하는 역할을 하는 함수MakeWebHandler는 기본적으로 "핸들러의 컨테이너"로 볼 수 있으며, 각 URL 경로에 대해 어떤 핸들러가 요청을 처리할지를 설정 gorilla/mux 라이브러리를 사용하여 mux라는 라우터 객체를 생성하고, 각 HTTP 요청의 경로(URL path)와 해당 요청을 처리할 함수(handler function)를 연결핸들러 함수는 HTTP 요청을 처리하고, 응답을 반환하는 함수
MakeWebHandler 함수에서는 여러 핸들러 함수를 각각의 경로에 등록하고 있다.mux 라우터는 클라이언트로부터 들어오는 요청의 URL을 분석하고, 등록된 경로와 일치하는 핸들러 함수를 찾아 호출
- 핸들러 함수란?
: HTTP 요청을 처리하는 기본 구성 요소,net/http패키지에 정의된http.Handler인터페이스를 구현
: 요청 정보를 분석하고, 적절한 로직을 수행한 뒤 응답을 반환하는 일련의 과정을 담당
: 이를 위해 요청 객체(http.Request)에서 URL 파라미터, 헤더, 본문 데이터 등을 읽어 로직을 처리
- 웹 서버의 특정 요청(URL 경로, HTTP 메소드 등)에 응답하는 데 사용
- 핸들러 함수는 특정 시그니처를 따르며, 이 시그니처는 두 개의 파라미터를 받는다:
-w http.ResponseWriter: 클라이언트에게 HTTP 응답을 보낼 수 있는 인터페이스, 이 객체를 통해 응답 상태 코드/헤더/본문을 설정할 수 있다.
-r *http.Request: 클라이언트로부터 받은 요청의 모든 정보를 담고 있다. 이 객체에서는 요청 메소드, 헤더, URL 파라미터, 본문 등에 접근할 수 있다.http.Handle또는http.HandleFunc를 사용하여 특정 경로와 연결된다.
-http.HandleFunc는 함수를 직접 등록할 수 있게 해주며, 더 간단하게 사용할 수 있다.
type Students []Student
// Students는 []Student라는 슬라이스 타입을 기반으로 만들어진 사용자 정의 타입
// []Student는 Student 구조체 인스턴스의 컬렉션, 여러 학생의 데이터를 그룹화하고 관리
// Students는 []Student와 같은 데이터를 가지며 특정 메서드를 추가할 수 있는 기능을 제공
/*
sort 패키지에 정의된 sort.Interface 인터페이스 구현
-> sort 패키지의 함수들을 사용하여 해당 슬라이스를 정렬할 수 있다.
- sort.Interface는 Go 언어의 표준 라이브러리 sort 패키지에 정의된 인터페이스
- 아래 세 메서드는 sort 패키지의 여러 정렬 함수들이 데이터를 정렬할 때 필요한 연산을 제공
- 사용자가 자신의 타입에 대해 sort.Interface를 구현하면,
-> 그 타입의 슬라이스에 대해 sort.Sort 등의 함수들을 사용할 수 있다
*/
func (s Students) Len() int { // 1) Len() int: 슬라이스의 길이 반환
return len(s)
}
func (s Students) Swap(i, j int) { // 2) Swap(i, j int): 슬라이스 내 두 요소의 위치 교환
s[i], s[j] = s[j], s[i]
}
func (s Students) Less(i, j int) bool { // 3) Less(i, j int) bool: 두 요소의 순서를 비교
return s[i].Id < s[j].Id
// i번째 요소가 j번째 요소보다 "작은지" 여부를 판단하여 정렬 순서를 결정
// Id를 기준으로 학생들을 오름차순(ascend)으로 정렬
}
// 1. 저장된 모든 학생 정보를 JSON 형식으로 반환하는 핸들러
// /students 경로로 "GET" 요청이 들어왔을 때 호출되는 함수
func GetStudentListHandler(w http.ResponseWriter, r *http.Request) {
list := make(Students, 0) // 길이가 0인 슬라이스 생성
for _, student := range students {
list = append(list, student)
sort.Sort(list) // ID를 기준으로 학생 목록을 정렬
w.WriteHeader(http.StatusOK)
}
// students 맵에서 모든 학생 데이터를 Students 슬라이스에 추가한 다음, 이를 Id 순서대로 정렬
// 응답의 상태 코드를 200 OK로 설정
w.Header().Set("Content-Type", "application/json") // 응답의 콘텐츠 타입을 application/json으로 설정
json.NewEncoder(w).Encode(list) // 정렬된 학생 목록을 JSON 형식으로 인코딩하여 응답 본문에 작성
}
// 2. 요청된 ID에 해당하는 학생 정보를 JSON 형식으로 반환
func GetStudentHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // mux.Vars() 호출해 인수 가져온다
id, _ := strconv.Atoi(vars["id"]) // URL에서 id 파라미터를 정수로 변환
student, ok := students[id]
if !ok {
w.WriteHeader(http.StatusNotFound) // 학생 데이터를 찾지 못한 경우 404 에러 반환
return
}
w.WriteHeader(http.StatusOK) // 성공 상태 코드 설정
w.Header().Set("Content-Type", "application/json") // 내용 타입 설정
json.NewEncoder(w).Encode(student) // 학생 데이터를 JSON 형식으로 인코딩 후 응답
}
// 3. 새로운 학생 데이터를 받아서 시스템에 추가
func PostStudentHandler(w http.ResponseWriter, r *http.Request) {
var student Student // 새로운 학생 정보를 저장할 Student 타입 변수 선언
// 요청 본문에서 JSON 형식의 데이터를 읽어와 Student 구조체로 디코딩
err := json.NewDecoder(r.Body).Decode(&student)
if err != nil {
w.WriteHeader(http.StatusBadRequest) // JSON 디코딩 실패 시, HTTP 400 (Bad Request) 상태 코드 반환
return
}
lastId++ // 글로벌 lastId 변수를 증가시켜 새로운 학생에게 유니크한 ID 할당
student.Id = lastId // 새로운 ID를 학생의 Id 필드에 설정
students[lastId] = student // students 맵에 새 학생 정보를 추가
w.WriteHeader(http.StatusCreated) // 데이터 추가 성공 시, HTTP 201 (Created) 상태 코드 반환
}
// 4. 주어진 ID에 해당하는 학생 데이터를 제거
func DeleteStudentHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r) // URL 파라미터에서 데이터 추출
id, _ := strconv.Atoi(vars["id"]) // URL 경로에 포함된 id 파라미터를 정수로 변환
// students 맵에서 해당 ID를 키로 사용하여 학생 데이터 존재 여부 확인
_, ok := students[id]
if !ok {
w.WriteHeader(http.StatusNotFound) // 해당 ID의 학생이 존재하지 않는 경우, HTTP 404 (Not Found) 반환
return
}
// 해당 ID의 학생 데이터가 존재하는 경우, 맵에서 해당 학생 정보 삭제
delete(students, id)
w.WriteHeader(http.StatusOK) // 성공적으로 학생 데이터를 삭제한 경우, HTTP 200 (OK) 반환
}
HTTP 응답을 반환HTTP 응답이란?
: 클라이언트의 요청에 대해 서버가 반환하는 데이터로, 주요 구성 요소는 상태 코드/헤더/응답 본문
: 각 요소는 요청에 대한 결과 및 추가 정보를 제공
1) 상태 코드 (Status Code)
: 상태 코드는 요청이 성공적이었는지, 그리고 어떤 종류의 응답을 반환하는지를 나타내는 3자리 숫자
: 상태 코드의 첫 번째 숫자에 따라 다섯 가지 주요 범주로 나뉜다:
- 1xx (정보 응답): 요청을 받았으며 프로세스를 계속한다.
- 2xx (성공): 요청을 성공적으로 받았으며 이해하고 처리했다.
- 200 OK: 요청 성공적으로 처리.
- 201 Created: 요청이 성공적으로 이루어져 새 리소스가 생성됨.
- 204 No Content: 요청은 성공적이지만 반환할 컨텐츠가 없음.- 3xx (리다이렉션): 추가 조치가 필요하여 요청을 완료해야 한다.
- 301 Moved Permanently: 요청된 리소스가 영구적으로 새 위치로 이동됨.
- 302 Found: 요청된 리소스가 일시적 다른 위치로 이동됨.- 4xx (클라이언트 에러): 클라이언트가 잘못된 요청을 보냈을 때.
- 400 Bad Request: 서버가 요청을 이해할 수 없음.
- 404 Not Found: 요청한 리소스를 찾을 수 없음.
- 401 Unauthorized: 인증 없이 요청이 이루어졌음.- 5xx (서버 에러): 서버가 유효한 요청을 처리하지 못했을 때.
- 500 Internal Server Error: 서버 내부 오류.
- 503 Service Unavailable: 서비스 이용 불가.
2) 헤더 (Headers)
: HTTP 헤더는 요청과 응답에 대한 추가 정보를 제공한다.
: 이는 데이터 형식, 인코딩, 캐싱 정책, 인증 정보 등을 포함할 수 있다. 몇 가지 중요한 응답 헤더는 다음과 같다:Content-Type: 응답 본문의 미디어 타입을 설명 (예: application/json).
Set-Cookie: 클라이언트에 쿠키를 설정하도록 지시
Cache-Control: 응답 데이터의 캐싱 방법을 정의
Access-Control-Allow-Origin: CORS(Cross-Origin Resource Sharing) 정책을 지정
3) 응답 본문 (Response Body)
: 응답 본문은 클라이언트로 반환되는 실제 데이터를 포함한다.
: 이 데이터는 HTML, JSON, XML 등 다양한 형식일 수 있다.
- API 응답의 경우 주로 JSON 형식으로 데이터가 구성
func main() {
http.ListenAndServe(":3000", MakeWebHandler()) // 3000번 포트에서 HTTP 서버 시작
// MakeWebHandler에서 반환된 핸들러를 사용하여 요청 처리
// http://localhost:3000/students로 요청을 보내면, 서버는 두 개의 미리 정의된 학생 데이터를 JSON 형식으로 응답
// -> 이 데이터는 Id 기준으로 정렬된 상태로 반환
}
main 함수에서 http.ListenAndServe(":3000", MakeWebHandler())를 호출함으로써
MakeWebHandler에서 생성 및 반환된 mux 라우터가 서버의 요청 처리 로직을 담당서버는 클라이언트로부터 들어오는 요청을
[1] 서버 시작
: main 함수에서 net/http 패키지에 있는 http.ListenAndServe를 호출하여 서버를 시작
func ListenAndServe(addr string, handler http.Handler) erroraddr: 서버가 수신 대기할 호스트 주소와 포트 번호를 문자열로 지정handler: http.Handler 인터페이스를 구현하는 객체를 지정http.DefaultServeMux)가 사용된다.error: 서버 실행 중 발생하는 오류를 반환[2] 요청 수신 및 라우팅
: 클라이언트로부터 요청이 들어오면 gorilla/mux 라우터가 URL을 분석하고 적절한 핸들러 함수로 요청을 전달
[3] 요청 처리
: 핸들러 함수는 요청 데이터를 처리하고, 결과를 JSON 등의 형태로 클라이언트에게 응답
[4] 응답 반환
: 클라이언트는 서버로부터 응답을 받고, 이 데이터를 사용하여 웹 페이지를 업데이트하거나 다른 처리를 진행
클라이언트는 웹 브라우저, 모바일 앱, 또는 다른 서버일 수 있으며, REST API를 통해 서버와 데이터를 주고받는다.
클라이언트는 HTTP 메소드(GET, POST, DELETE 등)를 사용하여 API에 요청을 보낸다.