Go 29장. Go 언어로 만드는 웹 서버

wldbs._.·2024년 8월 28일
1

REST API & Go

목록 보기
2/5
post-thumbnail

교안: 공봉식, <Tucker의 Go 언어 프로그래밍>, 2021
Tucker Programming 유튜브 Link
교안 Github Link

Go 언어로 웹 서버 만들기

[Preview]
HTTP (Hypertext Transfer Protocol) 는 인터넷에서 데이터를 주고받는 통신 프로토콜

클라이언트/서버 구조에서 클라이언트는 서버에 "요청"을 하고, 서버는 클라이언트로 "응답"을 한다

HTTP는 "비연결성" 특징이 있다

  • 클라이언트와 서버가 계속 연결되어 있지 않고, 요청 후 응답 후 연결을 "끊어버린다"

HTTPS (Hypertext Transfer Protocol)는 HTTP의 암호화 통신 버전

  • HTTPS는 암호화 및 인증이 있는 HTTP

HTTP 프로토콜의 기본 통신 포트는 80포트, HTTPS 프로토콜의 기본 통신 포트는 443포트

HTTP 프로토콜을 사용하여 요청에 대한 응답을 하는 서버를 웹 서버 혹은 HTTP 서버
GO는 웹 서버를 쉽게 만들 수 있도록 기본 패키지로 net/http 패키지 제공

HTTP는 비연결성 통신이라 자원 낭비를 줄일 수 있지만, 클라이언트의 상태를 유지할 수 없다는 단점 있다

  • 하지만 쿠키나 세션을 사용하면 상태 정보 유지할 수 있다
    • "쿠키 cookie": "클라이언트"에서 데이터를 저장/관리하여 상태를 유지하는 기술
    • "세션 session": "서버"에서 데이터를 저장/관리하여 상태를 유지하는 기술

HTTP를 사용하면 다양한 요청에 대한 처리를 할 수 있다
HTTPS를 사용하면 통신 내용을 암호화하므로 더 안전하다
GO 언어를 이용해 만든 웹 서버는 뛰어난 성능을 자랑한다


HTTP 웹 서버 만들기

GO 언어에서는 net/http 패키지를 사용하여 웹 서버를 만들 수 있다
GO 언어에서 웹 서버를 만들려면 < 핸들러 등록 >< 웹 서버 시작 >이라는 두 단계를 거쳐야 한다


1. 핸들러 등록

각 HTTP 요청 URL 경로에 대응할 수 있는 핸들러를 등록한다

  • 핸들러: 각 HTTP 요청 URL이 수신되었을 때, 그것을 처리하는 함수 또는 객체

핸들러는 HandleFunc() 함수로 등록

    1. http.HandleFunc는 URL 패턴과 이 패턴에 대응하는 함수를 등록하는 방법
    • 등록된 함수는 http.HandlerFunc 타입의 함수
    • 이 타입은 func(http.ResponseWriter, *http.Request) 시그니처를 가진 함수를 http.Handler 인터페이스로 래핑
    1. Handle() 함수로는 http.Handler() 인터페이스를 구현한 객체 등록
  • http.Handle 함수는 http.Handler 인터페이스를 직접 구현하는 객체를 사용하여 특정 URL 패턴에 대한 요청을 처리
    • http.Handler 인터페이스는 단 하나의 메서드인 ServeHTTP(http.ResponseWriter, *http.Request)를 가짐

-> 그러면 URL 경로에 해당하는 HTTP 요청 수신 시

  • 핸들러에 해당하는 함수를 호출하거나,
  • http.Handler 객체의 인터페이스인 ServeHTTP() 메서드를 호출하여 요청에 따른 로직 수행할 수 있다

func IndexPathHandler(w http.ResponseWriter, r *http.Request){
	...
}

http.HandelFunc("/", IndexPathHandler)
  • IndexPathHandler() 함수 정의
  • IndexPathHandler() 함수를 http.HandlerFunc 타입으로 변환하여 http.Handler 인터페이스를 만족하게 한다
  • http.HandleFunc() 함수로 "/" 경로 에 대해서 IndexPathHandler 함수 등록
    • -> "/" 경로에 해당하는 HTTP 요청을 수신할 때, IndexPathHandler() 함수 호출한다!

[추가]
http 패키지의 Request 구조체에는 HTTP 요청 정보가 담겨있다.

  • HTTP 요청 메서드 정보 (string) : GET, POST 등
  • URL 정보 (*url.URL) : HTTP 요청 보낸 URL 정보
    • URL 정보 이용해서 URL에 포함된 데이터를 쿼리해올 수 있다
  • HTTP 프로토콜 버전 정보 (string) : HTTP 1.0인지 2.0인지
  • HTTP 요청 헤더 정보 (Header) : 맵 형태로 변환되어 저장된다 [Host, accept-encoding, Accept-Language 등]
  • 바디 정보 (io.ReadCloser) : HTTP 요청의 실제 데이터를 담고 있다
    • io.ReadCloser 인터페이스 통해서 데이터 읽어올 수 있다
  • 등등..그외

2. 웹 서버 시작

각 경로에 대한 핸들러 등록을 마치면, 본격적으로 웹 서버를 시작하게 된다
ListenAndServe() 함수 호출해 웹 서버 시작 : ListenAndServe(addr string, handler Handler) error

  • addr : HTTP 요청을 수신하는 주소, 일반적으로 ":3000"과 같이 요청 수신하는 포트 번호 작성
  • handler : 핸들러 인스턴스, nil로 값 넣으면 디폴트 핸들러(DefaultServeMux) 실행된다, 패키지 함수인 http.HandleFunc()로 핸들러 함수 등록할 때는 두 번째 인수로 nil 넣어준다.
    • DefaultServeMuxhttp.HandleFunc() 함수 호출해 등록된 핸들러 사용

localhost란 현재 컴퓨터를 나타내는 키워드

  • http://localhost:3000 은 현재 컴퓨터의 3000번 포트에 HTTP 요청을 보내라는 뜻

"/"는 루트 경로

  • 도메인 주소 외 하위 경로가 없는 경우

http.Request에는 클라이언트에서 보낸 메서드(method), 헤더(header), 바디(body) 같은 HTTP 요청 정보를 가진다.

fmt 패키지의 Fprint()는 출력 스트림에 값을 쓰는 함수

  • fmt 패키지의 Print() 함수가 표준 출력 스트림으로 출력 고정된다
  • Fprint()는 지정한 출력 스트림에 출력한다
    • 여기서는 인수로 받은 http.ResponseWriter 타입을 출력 스트림으로 지정
    • http.ResponseWriter 타입에 값을 쓰면 HTTP 응답으로 전송된다

웹 서버는 위와 같이 핸들러를 먼저 등록하고, ListenAndServe를 통해서 웹 서버를 시작한다.
그리고 사용자에 요청을 보내면 등록된 핸들러가 있는지 확인하고 핸들러를 실행한다.


HTTP 동작 원리

우리가 웹 브라우저에 https://goldenrabbit.co.kr:3000을 입력한 뒤 enter 키를 눌렀을 때 일어나는 일을 생각해보자.

  • 도메인 : goldenrabbit.co.kr, 포트 번호 : 3000

1) 웹 브라우저는 먼저 도메인 네임 시스템 Domain Name System에 도메인에 해당하는 IP 주소 요청

  • IP 주소: 네트워크에서 컴퓨터 구별하는 숫자로 된 기호, 숫자로 되어있어서 외우기 힘들다 -> 문자열로 된 도메인 주소 사용
    • IP 주소가 목적지(컴퓨터) 나타낸다면 -> 포트 번호는 수신한 데이터 놓을 창구와 같다
    • 즉, IP 주소는 컴퓨터 자체를, 포트 번호는 해당 컴퓨터 내 데이터를 수신할 수 있는 창구와 같다.
    • 컴퓨터는 0 ~ 65535 포트 가진다

2) 포트 번호 없이 http://goldenrabbit.co.kr 입력하면?

3) https://

  • 데이터를 보내는 통신 규약으로, HTTPS를 사용하겠다는 것을 나타낸다
  • HTTPS는 HTTP 규약에 보안 기능을 추가한 통신 규약

4) HTTP

  • HTTP(Hypertext Transfer Protocol)는 하이퍼텍스트 전송 규약의 약자로, 하이퍼텍스트를 전송하는 규약
  • 하이퍼텍스트 : 하이퍼링크를 포함한 멀티미디어 텍스트, 문자/그림/이미지 등의 멀티미디어 포함, 다른 문서로 연결되는 링크 제공하는 문서 포맷
    • 웹에서 하이퍼텍스트 문서 사용하기 때문에 문자/이미지/음악/영상 등을 볼 수 있다
    • 링크 클릭해서 다른 페이지로 연결할 수 있다
    • 이 하이퍼텍스트 문서를 만들 수 있는 문서 포맷이 하이퍼텍스트 마크업 언어의 약자인 HTML 포맷

5) 웹 서버
: 특정 포트에서 대기하며 사용자의 HTTP 요청에 HTTP 응답을 전송하는 서버, 이때 응답은 일반적으로 HTML 문서 전송


HTTP 쿼리 인수 사용하기

HTTP 요청에 포함된 쿼리 인수를 읽고 사용하는 방법을 알아보자

HTTP 요청을 만들 때 필요한 인수를 쿼리 인수로 담을 수 있다

  • 쿼리 인수는 URL 끝에 붙여넣는 인수
  • http://localhost?id=1&name=abcd 와 같이 쿼리 인수 입력 : id와 name을 인수로, 값은 각각 1과 abcd
    • URL 뒤에 ?를 붙여서 쿼리 인수가 시작됨을 표시
    • 각 인수는 key=value 형태로 입력
    • 2개 이상의 인수를 쓸 때는 &를 사용해서 인수 연결

ServeMux 인스턴스 이용하기

앞서 ListenAndServe() 함수 두 번째 인수로 nil 넣어서 DefaultServeMux 사용한다 학습

DefaultServeMux 사용하면 http.HandleFunc() 함수 같은 패키지 함수들을 이용해서 등록한 핸들러를 사용하기 때문에, 다양한 기능을 추가하기 어려운 문제가 있다

http.NewServeMux() 함수 이용해 새로운 ServeMux 인스턴스 생성

  • http.HandleFunc() 함수는 DefaultServeMux에 핸들러를 등록하는 반면,
  • mux.HandleFunc() 메서드는 ServeMux 인스턴스에 핸들러 등록

ServeMux 인스턴스를 사용하면 핸들러에 다양한 기능을 추가하기가 쉬워진다.

[추가]
MUX : multiplexer의 약자로, 여러 입력 중 하나를 선택해서 반환하는 디지털 장치

  • 웹 서버는 각 URL에 해당하는 핸들러를 등록한 다음,
  • HTTP 요청이 왔을 때 URL에 해당하는 핸들러를 선택해서 실행하는 방식
    • 이 핸들러를 선택하고 실행하는 구조체 이름이 Mux를 제공한다 해서 ServeMux라 부른다.
    • 비슷한 의미인 라우터 router라고 말하기도 한다

파일 서버

HTML은 하이퍼텍스트 문서 포맷으로 문자뿐 아니라, 이미지나 음악 등 멀티미디어 컨텐츠를 포함할 수 있다
HTML 문서가 이미지나 음악 데이터를 직접 포함하는 형태가 아니라, 이미지나 음악 파일의 경로 URL을 포함하는 형태로 데이터를 담는다

  • 이미지 데이터를 직접 가지지 않고, <img> 태그의 src 값으로 이미지 경로만 가진다
  • 그러면 웹 브라우저는 HTML을 표시하는 데 필요한 이미지 데이터를 다시 HTTP 요청을 통해서 가져온다
  • 이미지 요청을 받은 웹 서버는 이미지 경로에 해당하는 데이터를 반환해줌으로써 웹 브라우저가 화면에 이미지를 표시할 수 있도록 한다
func main(){
	http.HandleFunc("/", http.FileServer(http.Dir("static")))
    http.ListenAndServe(":3000", nil)
}
  • static 폴더 아래 파일들을 제공하는 파일 서버를 만든다
  • "/" 경로에 대한 요청이 올때, static 폴더 아래 있는 파일들을 제공한다

JSON 데이터 전송

HTTP는 하이퍼텍스트, 즉 HTML 문서를 전송하는 프로토콜이지만 HTML 문서뿐 아니라 이미지나 다양한 데이터도 전송 가능

JSON은 자바스크립트 오브젝트 표기법의 약자로, 말 그대로 자바스크립트에서 오브젝트를 표현하는 방법으로 사용되는 포맷

  • 오브젝트 시작은 { 로 표기, } 로 종료
  • 필드는 "key":value 형태로 표기
  • 각 필드는 , 로 구분
  • 배열은 [ ] 로 표기
  • 문자열은 " "로 묶어서 표기
func StudentHandler(w http.ResponseWriter, r *http.Request){
	var student = Student{"aaa", 16, 87}
    data, _ := json.Marshal(student) // Student 객체를 JSON 포맷으로 변환
    w.Header().Add("content-type", "application/json") // JSON 포맷임을 표시
    w.WriteHeader(http.StatusOk)
    fmt.Fprint(w, string(data) // ResponseWriter로 JSON 데이터를 문자열 타입으로 변환해 사용
}
  • w는 응답을 작성하는 데 사용되는 http.ResponseWriter 객체이고, r은 요청 정보를 담고 있는 *http.Request 객체
  • Student 구조체의 인스턴스를 생성하고 초기화, 이름, 나이, 점수 순으로 필드를 초기화
  • json.Marshal 함수는 주어진 객체(student)를 JSON 포맷의 바이트 슬라이스로 변환
    • 결과는 [ ]byte 타입으로 반환된다
  • json.NewDecoder(res.Body).Decode(student)를 통해 JSON 데이터를 Student 객체로 변환

HTTPS 웹 서버 만들기

HTTPS를 지원하는 웹 서버를 만들어보자

HTTPS는 HTTP에 보안 기능을 강화한 프로토콜

  • 본래 HTTP는 보안을 염두에 두지 않고 설계된 프로토콜 -> 모든 요청이 평문(일반 문자열) => 보안에 매우 취약
  • 이를 방지하고자 나온 게 HTTPS

웹 서버를 시작할 때 ListenAndServe() 함수가 아니라, ListenAndServeTLS() 함수를 사용

  • 호출 인수로 인증서와 비밀키 파일을 넣어준다는 점만 달라진다!

공개키 암호화 방식

HTTPS는 기존 HTTP 요청과 응답을 공개키 암호화 방식을 사용해서 암호화한 프로토콜

  • 패킷이 암호화되기 때문에 해커가 스니핑을 한다고 해도 어떤 내용인지 알 수 없다 -> 개인정보 노출 X
  • 공개키 암호화 방식은 공개키와 비밀키 두 가지 키를 생성해서
    • 공개키는 클라이언트에게 알려준다
    • 비밀키는 서버에 비공개 상태로 놔둔다
  • 클라이언트에서 HTTPS 요청을 보낼 때 공개키로 "암호화", 서버는 비밀키를 이용해서 다시 원문으로 돌리는 "복호화" 한다
    • 그래서 해커가 공개키를 획득한다 해도, 비밀키가 없으면 메시지 복호화할 수 없어서 안전
  • 공개키 암호화 방식은 암호화와 복호화에 쓰이는 키가 서로 다르기 깨문에 비대칭 암호화 방식
  • 공개키 암호화 방식에서는 비밀키가 노출되지 않도록 각별히 주의해야 한다

인증서와 키 생성

HTTPS 서버를 실행하려면 인증서와 비밀키를 생성해야 한다.
본래 인증서는 인증기관에서 발급해야 한다. 혹은 셀프 인증

  • 인증서 발급하려면 openssl 프로그램 설치
  • 대표적인 인증 알고리즘인 x509 사용해서 인증서 발급
  • 공개키는 localhost.crt 파일에 포함되어 있다
  • HTTPS 서버를 실행하여 클라이언트 접속 시 클라이언트에 localhost.crt 파일로 인증 정보와 공개키 전송하게 된다
profile
공부 기록용 24.08.05~ #LLM #RAG

0개의 댓글