TIL 08 - HTTP & Gin-Gonic 1(Golang)

프동프동·2023년 2월 10일
0

TIL

목록 보기
8/46
post-thumbnail

핸들러 등록

핸들러 :HTTP 요청 URL이 수신됐을 때 그것을 처리하는 함수 또는 객체를 의미

  • 사용 방법
    • HandleFunc() 함수를 통해 등록할 수 있다.
      - http.Handler 인터페이스를 구현한 객체를 등록하고 해당 객체의 인터페이스인 ServeHTTP() 메서드를 호출하여 요청에 따른 로직을 수행할 수 있다.

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

http.Request 구조체

http.Requse 구조체에는 HTTP 요청 정보가 담겨있다.

type Request struct{
	// 요청 메서드 정보를 가지고 있다.
	Method string
	// HTTP 요청을 보낸 URL 정보를 담고 있다.
	URL *url.URL
	// HTTP 프로토콜 버전 정보
	Proto string
	ProtoMajor int
	ProtoMajor int
	// 맵 형태로 변환되어 저장되어 있다.
	Header Header
	// HTTP 요청의 실제 데이터를 가지고 있다.
	Body io.ReadCloser
	...
}

웹 서버 시작

ListenAndServe() 함수를 통해 웹 서버를 시작할 수 있다.

func ListenAndServe(addr string, handler Handler) error
  • 사용 방법
    • 첫번째 인수 : HTTP 요청을 수신하는 주소, 보통 포트 번호를 적어주게 된다.
      • ex. “:3000”
    • 두번째 인수 : 핸들러 인스턴스, nil 입력 시 디폴트 핸들러가 실행된다.
      • http.HandleFunc()로 핸들러 함수를 등록할 떄는 두번째 인수에 nil을 넣어준다.
      • nil을 넣어주면 DefaultServeMux를 사용한다. DefaultServeMux는 http.HandleFunc()함수를 호출해 등록된 핸들러들을 사용한다.
package main

import (
	"fmt"
	"net/http"
)

// 함수의 인자는 반드시 http.ResponseWriter, *http.Request 받아야 한다.
func IndexPathHandler(w http.ResponseWriter, r *http.Request) {
// 지정한 출력 스트림에 값을 쓴다.
// http.ResponseWriter에 값을 쓰면 HTTP 응답으로 전송된다.
	fmt.Fprint(w, "Hello World")
}
func main() {
	// "/" 요청에 대한 핸들러 함수 등록
	http.HandleFunc("/", IndexPathHandler)
	// 웹 서버 시작
	http.ListenAndServe(":3000", nil)
}

HTTP 쿼리 인수 사용하기

URL 끝에 붙여넣는 인수를 말하는 것

http://localhost?id=1&name=abcd
  • 사용 방법
    • Client

      • URL 뒤에 ? 를 붙여서 쿼리 인수가 시작됨을 표시한다.
      • key=value 형태로 입력
      • 2개 이상의 인수를 쓸 때는 &를 사용해서 인수들을 연결한다.
    • Server
      - r.URL.Query() 함수 사용하여 쿼리 인수 가져오기
      - 반환 값 : map[string][]string
      - ex. map[id:[5] name:[FDongFDong]]
      - Get() 메서드를 사용해서 인수 값을 꺼낼 수 있다.

      package main
      
      import (
      	"fmt"
      	"net/http"
      	"strconv"
      )
      
      func barHandler(w http.ResponseWriter, r *http.Request) {
      	// 쿼리 인수 가져오기
      	values := r.URL.Query()
      	// 특정 키 값이 있는지 확인
      	name := values.Get("name")
      	// 없으면 World를 대입한다.
      	if name == "" {
      		name = "World"
      	}
      	// 쿼리에서 id 값을 가져와서 Int형으로 변환한다.
      	// 변환되면 변환된 값, 실패 시 에러 반환
      	id, _ := strconv.Atoi(values.Get("id"))
      	fmt.Fprintf(w, "Hello %s! id:%d", name, id)
      
      }
      func main() {
      	// "./bar"에 대한 경로로 들어왔을 때 barHandler 핸들러 등록
      	http.HandleFunc("/bar", barHandler)
      	http.ListenAndServe(":3000", nil)
      }

      Untitled

      ServeMux 인스턴스

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

    • 사용 방법
      - http.NewServeMux() 함수를 통해 ServeMux 인스턴스 생성
      - 생성된 ServerMux 인스턴스의 HandleFunc() 함수를 통해 핸들러 등록
      - http.ListenAndServe() 함수 두번째 인수로 앞서 생성한 ServeMux 등록

      package main
      
      import (
      	"fmt"
      	"net/http"
      )
      
      func indexPathHandler(w http.ResponseWriter, r *http.Request) {
      	fmt.Fprint(w, "Hello World")
      }
      func barPathHandler(w http.ResponseWriter, r *http.Request){
      	fmt.Fprint(w, "Hello Bar")
      }
      func main() {
      	// ServeMux 인스턴스 생성
      	mux := http.NewServeMux()
      	// 인스턴스에 핸들러 등록
      	mux.HandleFunc("/", indexPathHandler)
      	mux.HandleFunc("/bar",barPathHandler)
      	// mux 인스턴스 등록
      	http.ListenAndServe(":3000", mux)
      }
    • Mux

      • multiplexer의 약자, 라우터라고 말하기도 한다.

Toy Project : Todo App 만들기


Tucker Go 언어 프로그래밍 예제를 통해 학습하였습니다.

학습 목표

  • Go 언어를 통해 RESTful한 서버 구축
  • http 프로토콜을 이용한 데이터 송수신
  • 라이브러리 학습

사용 스킬

  • Front
    • JQuery
    • Bootstrap
    • CSS
  • Back
    • Go

기능

Todo List 불러오기(GET)

Untitled

Todo List 항목 삭제하기(DELETE)

화면_기록_2022-12-14_오후_5_01_46_AdobeExpress.gif

Todo List 내용 추가하기(POST)

화면_기록_2022-12-14_오후_5_05_24_AdobeExpress.gif

사용 라이브러리

negroni(미들웨어)

"github.com/urfave/negroni"

기본적으로 파일서버를 가지고 있으며 로그 기능을 제공한다.

  • 사용 방법
    func main() {
    	mux := pat.New()
    	n := negroni.Classic()
    	n.UseHandler(mux)
    
    	http.ListenAndServe(":3000",n)

render

"github.com/unrolled/render"

JSON, XML, 바이너리, HTML 템플릿을 쉽게 렌더링 할 수 있는 기능을 제공하는 패키지

  • 사용 방법
// 전역 변수 등록
var rd *render.Render
// 인스턴스 생성
rd = render.New()
// 첫번째 인자: ResponseWriter
// 두번째 인자: StatusCode
// 세번째 인자: Json으로 바꾸고 싶은 인스턴스
rd.JSON(w, http.StatusOK, user)

gorilla/mux

"github.com/gorilla/mux"

RESTful API를 쉽게 만들 수 있도록 지원해주는 패키지

실행 코드

디렉터리 구조

todoApp - public - todo.css
								 - todo.html
								 - todo.js

			  - app.   - app.go
				- main.go

main.go

package main

import (
	"net/http"
	"todoApp/app"

	"github.com/urfave/negroni"
)

func main() {

	m := app.MakeNewHandler()
	// negroni : 로그와 파일서버를 제공해주는 미들웨어
	n := negroni.Classic()
	n.UseHandler(m)

	err := http.ListenAndServe(":3000", n)
	if err != nil {
		panic(err)
	}
}

app.go

package app

import (
	"log"
	"net/http"
	"strconv"
	"time"

	"github.com/gorilla/mux"
	"github.com/unrolled/render"
)

var rd *render.Render

type Todo struct {
	ID        int       `json:"id"`
	Name      string    `json:"name"`
	Completed bool      `json:"completed"`
	CreatedAt time.Time `json:"createdAt"`
}

var todoMap map[int]*Todo

// /todo.html로 리다이렉트 시키는 함수
func indexHandler(w http.ResponseWriter, r *http.Request) {
	http.Redirect(w, r, "/todo.html", http.StatusTemporaryRedirect)
}

func getTodoListHandler(w http.ResponseWriter, r *http.Request) {
	list := []*Todo{}
	for _, v := range todoMap {
		list = append(list, v)
	}
	rd.JSON(w, http.StatusOK, list)
}

func addTestTodos() {
	todoMap[1] = &Todo{1, "Buy a milk", false, time.Now()}
	todoMap[2] = &Todo{2, "Buy a milk", true, time.Now()}
	todoMap[3] = &Todo{3, "Buy a milk", false, time.Now()}
	todoMap[4] = &Todo{4, "Buy a milk", false, time.Now()}
	todoMap[5] = &Todo{5, "Buy a milk", true, time.Now()}
}

func addTodoHandler(w http.ResponseWriter, r *http.Request) {
	name := r.FormValue("name")
	// id 생성
	id := len(todoMap) + 1
	todo := &Todo{id, name, false, time.Now()}
	todoMap[id] = todo
	rd.JSON(w, http.StatusOK, todo)

}

type Success struct {
	Success bool `json:"success`
}

func removeTodoHandler(w http.ResponseWriter, r *http.Request) {
	log.Printf("뭐지..\n")
	// id값을 뽑아내기 위함
	vars := mux.Vars(r)
	// 문자열을 숫자로 바꿔준다.
	id, _ := strconv.Atoi(vars["id"])
	// id 값이 기존 todoMap에 있는지 확인하여 있으면 지워준 후 클라이언트에게 true 값을 보내준다.
	if _, ok := todoMap[id]; ok {
		delete(todoMap, id)
		rd.JSON(w, http.StatusOK, Success{true})
	} else {
		rd.JSON(w, http.StatusOK, Success{false})
	}

}

func completeTodoHandler(w http.ResponseWriter, r *http.Request) {
	// id값 추출
	vars := mux.Vars(r)
	id, _ := strconv.Atoi(vars["id"])
	complete := r.FormValue("complete") == "true"
	// todoMap에 있는 경우 변경
	if todo, ok := todoMap[id]; ok {
		todo.Completed = complete
		rd.JSON(w, http.StatusOK, Success{true})
	} else {
		rd.JSON(w, http.StatusOK, Success{false})
	}

}
func MakeNewHandler() http.Handler {
	// todo를 인메모리에 저장하기 위한 변수선언
	todoMap = make(map[int]*Todo)
	// 테스트용 리스트 등록
	addTestTodos()

	rd = render.New()
	r := mux.NewRouter()
	r.HandleFunc("/todos", getTodoListHandler).Methods("GET")
	r.HandleFunc("/todos", addTodoHandler).Methods("POST")
	r.HandleFunc("/todos/{id:[0-9]+}", removeTodoHandler).Methods("DELETE")
	r.HandleFunc("complete-todo/{id:[0-9]+}", completeTodoHandler).Methods("GET")
	// 인덱스 경로로 들어오면 리다이렉트시킨다.
	r.HandleFunc("/", indexHandler)

	return r
}

todo.js

(function ($) {
  'use strict';
  $(function () {
    var todoListItem = $('.todo-list');
    var todoListInput = $('.todo-list-input');
    $('.todo-list-add-btn').on('click', function (event) {
      event.preventDefault();

      var item = $(this).prevAll('.todo-list-input').val();
      // POST로 Item을 보낸 후 서버에서 값이 오면 addItem함수를 호출한다.
      if (item) {
        $.post('/todos', { name: item }, addItem);
        todoListInput.val('');
      }
    });

    var addItem = function (item) {
      if (item.completed) {
        todoListItem.append(
          "<li class='completed'" +
            " id='" +
            item.id +
            "'><div class='form-check'><label class='form-check-label'><input class='checkbox' type='checkbox' checked='checked' />" +
            item.name +
            "<i class='input-helper'></i></label></div><i class='remove mdi mdi-close-circle-outline'></i></li>"
        );
      } else {
        todoListItem.append(
          '<li ' +
            " id='" +
            item.id +
            "'><div class='form-check'><label class='form-check-label'><input class='checkbox' type='checkbox' />" +
            item.name +
            "<i class='input-helper'></i></label></div><i class='remove mdi mdi-close-circle-outline'></i></li>"
        );
      }
    };
    // GET /todos : todos 리스트를 불러오는 메서드
    $.get('/todos', function (items) {
      items.forEach((e) => {
        addItem(e);
      });
    });

    // GET /complete-todo/{id} : 체크박스의 상태를 전달하는 메서드
    todoListItem.on('change', '.checkbox', function () {
      var id = $(this).closest('li').attr('id');
      var $self = $(this);
      var complete = true;
      if ($(this).attr('checked')) {
        complete = false;
      }
      $.get('complete-todo/' + id + '?complete=' + complete, function (data) {
        if (complete) {
          $self.attr('checked', 'checked');
        } else {
          $self.removeAttr('checked');
        }

        $self.closest('li').toggleClass('completed');
      });
    });

    // DELETE todos/id todo 리스트 한가지 항목 삭제하는 메서드
    todoListItem.on('click', '.remove', function () {
      var id = $(this).closest('li').attr('id');
      var $self = $(this);
      $.ajax({
        url: '/todos/' + id,
        type: 'DELETE',
        success: function (data) {
          if (data.success) {
            $self.parent().remove();
          }
        },
      });
      console.log('?');
    });
  });
})(jQuery);
profile
좋은 개발자가 되고싶은

0개의 댓글