|
@@ -0,0 +1,198 @@
|
|
|
|
+package main
|
|
|
|
+
|
|
|
|
+import (
|
|
|
|
+ "flag"
|
|
|
|
+ "fmt"
|
|
|
|
+ "log"
|
|
|
|
+ "strconv"
|
|
|
|
+ "strings"
|
|
|
|
+ "time"
|
|
|
|
+
|
|
|
|
+ "github.com/panjf2000/gnet"
|
|
|
|
+)
|
|
|
|
+
|
|
|
|
+var res string
|
|
|
|
+var resBytes []byte
|
|
|
|
+
|
|
|
|
+type request struct {
|
|
|
|
+ proto, method string
|
|
|
|
+ path, query string
|
|
|
|
+ head, body string
|
|
|
|
+ remoteAddr string
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+type httpServer struct {
|
|
|
|
+ *gnet.EventServer
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+var errMsg = "Internal Server Error"
|
|
|
|
+var errMsgBytes = []byte(errMsg)
|
|
|
|
+
|
|
|
|
+type httpCodec struct {
|
|
|
|
+ req request
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (hc *httpCodec) Encode(c gnet.Conn, buf []byte) (out []byte, err error) {
|
|
|
|
+ if c.Context() == nil {
|
|
|
|
+ return appendHandle(out, res), nil
|
|
|
|
+ }
|
|
|
|
+ return appendResp(out, "500 Error", "", errMsg+"\n"), nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (hc *httpCodec) Decode(c gnet.Conn) ([]byte, error) {
|
|
|
|
+ buf := c.Read()
|
|
|
|
+ // process the pipeline
|
|
|
|
+ leftover, err := parseReq(buf, &hc.req)
|
|
|
|
+ // bad thing happened
|
|
|
|
+ if err != nil {
|
|
|
|
+ c.SetContext(err)
|
|
|
|
+ return nil, err
|
|
|
|
+ } else if len(leftover) == len(buf) {
|
|
|
|
+ // request not ready, yet
|
|
|
|
+ return nil, nil
|
|
|
|
+ }
|
|
|
|
+ c.ResetBuffer()
|
|
|
|
+ return buf, nil
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (hs *httpServer) OnInitComplete(srv gnet.Server) (action gnet.Action) {
|
|
|
|
+ log.Printf("HTTP server is listening on %s (multi-cores: %t, loops: %d)\n",
|
|
|
|
+ srv.Addr.String(), srv.Multicore, srv.NumLoops)
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (hs *httpServer) React(c gnet.Conn) (out []byte, action gnet.Action) {
|
|
|
|
+ data := c.ReadFrame()
|
|
|
|
+ // process the pipeline
|
|
|
|
+ if c.Context() != nil {
|
|
|
|
+ // bad thing happened
|
|
|
|
+ out = errMsgBytes
|
|
|
|
+ action = gnet.Close
|
|
|
|
+ return
|
|
|
|
+ } else if data == nil {
|
|
|
|
+ // request not ready, yet
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ // handle the request
|
|
|
|
+ out = resBytes
|
|
|
|
+ return
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func main() {
|
|
|
|
+ var port int
|
|
|
|
+ var multicore bool
|
|
|
|
+
|
|
|
|
+ // Example command: go run main.go --port 8080 --multicore true
|
|
|
|
+ flag.IntVar(&port, "port", 8080, "server port")
|
|
|
|
+ flag.BoolVar(&multicore, "multicore", true, "multicore")
|
|
|
|
+ flag.Parse()
|
|
|
|
+
|
|
|
|
+ res = "Hello, World!"
|
|
|
|
+ resBytes = []byte(res)
|
|
|
|
+
|
|
|
|
+ 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, "Content-Type: text/plain\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
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+// 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 := string(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
|
|
|
|
+}
|