Created
May 16, 2020 04:58
-
-
Save birowo/d94e2a8b58eeb854ebfe56d12f241a7e to your computer and use it in GitHub Desktop.
desc.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 2019 Andy Pan. All rights reserved. | |
// Copyright 2017 Joshua J Baker. All rights reserved. | |
// Use of this source code is governed by an MIT-style | |
// license that can be found in the LICENSE file. | |
package main | |
import ( | |
"flag" | |
"fmt" | |
"log" | |
"strconv" | |
"strings" | |
"time" | |
"unsafe" | |
"github.com/panjf2000/gnet" | |
) | |
var res string | |
type request struct { | |
proto, method string | |
path, query string | |
head, body string | |
remoteAddr string | |
} | |
var errMsg = "Internal Server Error" | |
var errMsgBytes = []byte(errMsg) | |
type httpServer struct { | |
*gnet.EventServer | |
} | |
func (hs *httpServer) OnInitComplete(srv gnet.Server) (action gnet.Action) { | |
fmt.Println("OnInitComplete event") | |
log.Printf("HTTP server is listening on %s (multi-cores: %t, loops: %d)\n", | |
srv.Addr.String(), srv.Multicore, srv.NumEventLoop) | |
return | |
} | |
type Ctx struct { | |
req *request | |
err error | |
} | |
func (hs *httpServer) OnOpened(c gnet.Conn) (out []byte, action gnet.Action) { | |
fmt.Println("OnOpened event") | |
c.SetContext(&Ctx{new(request), nil}) //request ditaruh di context (connection scope) | |
return | |
} | |
func (hs *httpServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) { | |
fmt.Println("React event") | |
ctx := c.Context().(*Ctx) | |
if ctx.err != nil { | |
// bad thing happened | |
out = errMsgBytes | |
action = gnet.Close | |
return | |
} | |
// handle the request | |
out = frame | |
return | |
} | |
func (hs *httpServer) OnClosed(c gnet.Conn, err error) (action gnet.Action) { | |
fmt.Println("OnClosed event") | |
return | |
} | |
type httpCodec struct { | |
// req request //request tidak tepat kalau ditaruh di sini(main scope) | |
} | |
func (hc *httpCodec) Encode(c gnet.Conn, buf []byte) (out []byte, err error) { | |
fmt.Println("Encode") | |
ctx := c.Context().(*Ctx) | |
if ctx.err == nil { | |
return buf, nil | |
} | |
return appendResp(out, "500 Error", "", errMsg+"\n"), nil | |
} | |
func (hc *httpCodec) Decode(c gnet.Conn) (out []byte, err error) { | |
fmt.Println("Decode") | |
buf := c.Read() | |
c.ResetBuffer() | |
ctx := c.Context().(*Ctx) | |
// process the pipeline | |
var leftover []byte | |
pipeline: | |
leftover, err = parseReq(buf, ctx.req) | |
// bad thing happened | |
if err != nil { | |
ctx.err = err | |
return nil, err | |
} else if len(leftover) == len(buf) { | |
// request not ready, yet | |
return | |
} | |
//percobaan: | |
time.Sleep(9 * time.Second) | |
switch ctx.req.path { | |
case "/abc": | |
res = "abc" | |
case "/def": | |
res = "def" | |
} | |
/* | |
delay 9 detik untuk test apakah suatu koneksi memblok koneksi lainnya | |
akses http://localhost/abc (setelah 9 detik akan tampil abc) kemudian sebelum 9 detik, | |
akses(di tab / browser lain) http://localhost/def (setelah 9 detik akan tampil def) | |
terbukti di percobaan ini, bahwa suatu koneksi tidak memblok koneksi lainnya | |
*/ | |
out = appendHandle(out, res) | |
buf = leftover | |
goto pipeline | |
} | |
func main() { | |
var port int | |
var multicore bool | |
// Example command: go run http.go --port 8080 --multicore=true | |
flag.IntVar(&port, "port", 8080, "server port") | |
flag.BoolVar(&multicore, "multicore", true, "multicore") | |
flag.Parse() | |
res = "Hello World!\r\n" | |
http := new(httpServer) | |
hc := new(httpCodec) | |
// Start serving! | |
log.Fatal(gnet.Serve(http, fmt.Sprintf("tcp://:%d", port), gnet.WithMulticore(multicore), gnet.WithCodec(hc))) | |
} | |
// appendHandle handles the incoming request and appends the response to | |
// the provided bytes, which is then returned to the caller. | |
func appendHandle(b []byte, res string) []byte { | |
return appendResp(b, "200 OK", "", res) | |
} | |
// appendResp will append a valid http response to the provide bytes. | |
// The status param should be the code plus text such as "200 OK". | |
// The head parameter should be a series of lines ending with "\r\n" or empty. | |
func appendResp(b []byte, status, head, body string) []byte { | |
b = append(b, "HTTP/1.1"...) | |
b = append(b, ' ') | |
b = append(b, status...) | |
b = append(b, '\r', '\n') | |
// b = append(b, "Server: gnet\r\n"...) | |
b = append(b, "Date: "...) | |
b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT") | |
b = append(b, '\r', '\n') | |
if len(body) > 0 { | |
b = append(b, "Content-Length: "...) | |
b = strconv.AppendInt(b, int64(len(body)), 10) | |
b = append(b, '\r', '\n') | |
} | |
b = append(b, head...) | |
b = append(b, '\r', '\n') | |
if len(body) > 0 { | |
b = append(b, body...) | |
} | |
return b | |
} | |
func b2s(b []byte) string { | |
return *(*string)(unsafe.Pointer(&b)) | |
} | |
// parseReq is a very simple http request parser. This operation | |
// waits for the entire payload to be buffered before returning a | |
// valid request. | |
func parseReq(data []byte, req *request) (leftover []byte, err error) { | |
sdata := b2s(data) | |
var i, s int | |
var head string | |
var clen int | |
var q = -1 | |
// method, path, proto line | |
for ; i < len(sdata); i++ { | |
if sdata[i] == ' ' { | |
req.method = sdata[s:i] | |
for i, s = i+1, i+1; i < len(sdata); i++ { | |
if sdata[i] == '?' && q == -1 { | |
q = i - s | |
} else if sdata[i] == ' ' { | |
if q != -1 { | |
req.path = sdata[s:q] | |
req.query = req.path[q+1 : i] | |
} else { | |
req.path = sdata[s:i] | |
} | |
for i, s = i+1, i+1; i < len(sdata); i++ { | |
if sdata[i] == '\n' && sdata[i-1] == '\r' { | |
req.proto = sdata[s:i] | |
i, s = i+1, i+1 | |
break | |
} | |
} | |
break | |
} | |
} | |
break | |
} | |
} | |
if req.proto == "" { | |
return data, fmt.Errorf("malformed request") | |
} | |
head = sdata[:s] | |
for ; i < len(sdata); i++ { | |
if i > 1 && sdata[i] == '\n' && sdata[i-1] == '\r' { | |
line := sdata[s : i-1] | |
s = i + 1 | |
if line == "" { | |
req.head = sdata[len(head)+2 : i+1] | |
i++ | |
if clen > 0 { | |
if len(sdata[i:]) < clen { | |
break | |
} | |
req.body = sdata[i : i+clen] | |
i += clen | |
} | |
return data[i:], nil | |
} | |
if strings.HasPrefix(line, "Content-Length:") { | |
n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64) | |
if err == nil { | |
clen = int(n) | |
} | |
} | |
} | |
} | |
// not enough data | |
return data, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment