핸들러 :HTTP 요청 URL이 수신됐을 때 그것을 처리하는 함수 또는 객체를 의미
HandleFunc() 함수를 통해 등록할 수 있다.
- http.Handler 인터페이스를 구현한 객체를 등록하고 해당 객체의 인터페이스인 ServeHTTP() 메서드를 호출하여 요청에 따른 로직을 수행할 수 있다.
func IndexPathHandler(w http.ResponseWriter, r *http.Request){
// ...
}
http.HandleFunc("/", IndexPathHandler)
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
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)
}
URL 끝에 붙여넣는 인수를 말하는 것
http://localhost?id=1&name=abcd
Client
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)
}
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
Tucker Go 언어 프로그래밍 예제를 통해 학습하였습니다.
"github.com/urfave/negroni"
기본적으로 파일서버를 가지고 있으며 로그 기능을 제공한다.
func main() {
mux := pat.New()
n := negroni.Classic()
n.UseHandler(mux)
http.ListenAndServe(":3000",n)
"github.com/unrolled/render"
JSON, XML, 바이너리, HTML 템플릿을 쉽게 렌더링 할 수 있는 기능을 제공하는 패키지
// 전역 변수 등록
var rd *render.Render
// 인스턴스 생성
rd = render.New()
// 첫번째 인자: ResponseWriter
// 두번째 인자: StatusCode
// 세번째 인자: Json으로 바꾸고 싶은 인스턴스
rd.JSON(w, http.StatusOK, user)
"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);