mirror of https://github.com/ledisdb/ledisdb.git
refactor http interface
This commit is contained in:
parent
d0e7698984
commit
e46554e9f6
|
@ -3,7 +3,6 @@ package server
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/siddontang/ledisdb/ledis"
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
. "github.com/siddontang/ledisdb/server/http"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
@ -134,7 +133,9 @@ func (app *App) httpServe() {
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
mux.Handle("/", &CmdHandler{app.Ledis()})
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
newClientHTTP(app, w, r)
|
||||||
|
})
|
||||||
|
|
||||||
svr := http.Server{Handler: mux}
|
svr := http.Server{Handler: mux}
|
||||||
svr.Serve(app.httpListener)
|
svr.Serve(app.httpListener)
|
||||||
|
|
|
@ -0,0 +1,254 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/siddontang/go-log/log"
|
||||||
|
"github.com/siddontang/ledisdb/ledis"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/ugorji/go/codec"
|
||||||
|
"gopkg.in/mgo.v2/bson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var allowedContentTypes = map[string]struct{}{
|
||||||
|
"json": struct{}{},
|
||||||
|
"bson": struct{}{},
|
||||||
|
"msgpack": struct{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpClient struct {
|
||||||
|
app *App
|
||||||
|
db *ledis.DB
|
||||||
|
ldb *ledis.Ledis
|
||||||
|
|
||||||
|
resp responseWriter
|
||||||
|
req *requestContext
|
||||||
|
}
|
||||||
|
|
||||||
|
type httpWriter struct {
|
||||||
|
contentType string
|
||||||
|
cmd string
|
||||||
|
w http.ResponseWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// http context
|
||||||
|
|
||||||
|
func newClientHTTP(app *App, w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
c := new(httpClient)
|
||||||
|
c.app = app
|
||||||
|
c.ldb = app.ldb
|
||||||
|
c.db, err = c.ldb.Select(0)
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.req, err = c.makeRequest(app, r, w)
|
||||||
|
if err != nil {
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.req.perform()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpClient) addr(r *http.Request) string {
|
||||||
|
addr := r.Header.Get("X-Forwarded-For")
|
||||||
|
if addr == "" {
|
||||||
|
addr = r.Header.Get("X-Real-IP")
|
||||||
|
if addr == "" {
|
||||||
|
addr = r.Header.Get("Remote-Addr")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpClient) makeRequest(app *App, r *http.Request, w http.ResponseWriter) (*requestContext, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
db, cmd, argsStr, contentType := c.parseReqPath(r)
|
||||||
|
|
||||||
|
c.db, err = app.ldb.Select(db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
contentType = strings.ToLower(contentType)
|
||||||
|
|
||||||
|
if _, ok := allowedContentTypes[contentType]; !ok {
|
||||||
|
return nil, fmt.Errorf("unsupported content type: '%s', only json, bson, msgpack are supported", contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := newRequestContext(app)
|
||||||
|
args := make([][]byte, len(argsStr))
|
||||||
|
for i, arg := range argsStr {
|
||||||
|
args[i] = []byte(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.cmd = cmd
|
||||||
|
req.args = args
|
||||||
|
req.remoteAddr = c.addr(r)
|
||||||
|
req.resp = &httpWriter{contentType, cmd, w}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *httpClient) parseReqPath(r *http.Request) (db int, cmd string, args []string, contentType string) {
|
||||||
|
|
||||||
|
contentType = r.FormValue("type")
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "json"
|
||||||
|
}
|
||||||
|
|
||||||
|
substrings := strings.Split(strings.TrimLeft(r.URL.Path, "/"), "/")
|
||||||
|
if len(substrings) == 1 {
|
||||||
|
return 0, substrings[0], substrings[1:], contentType
|
||||||
|
}
|
||||||
|
db, err := strconv.Atoi(substrings[0])
|
||||||
|
if err != nil {
|
||||||
|
cmd = substrings[0]
|
||||||
|
args = substrings[1:]
|
||||||
|
} else {
|
||||||
|
cmd = substrings[1]
|
||||||
|
args = substrings[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// http writer
|
||||||
|
|
||||||
|
func (w *httpWriter) genericWrite(result interface{}) {
|
||||||
|
|
||||||
|
m := map[string]interface{}{
|
||||||
|
w.cmd: result,
|
||||||
|
}
|
||||||
|
switch w.contentType {
|
||||||
|
case "json":
|
||||||
|
writeJSON(&m, w.w)
|
||||||
|
case "bson":
|
||||||
|
writeBSON(&m, w.w)
|
||||||
|
case "msgpack":
|
||||||
|
writeMsgPack(&m, w.w)
|
||||||
|
default:
|
||||||
|
log.Error("invalid content type %s", w.contentType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeError(err error) {
|
||||||
|
result := [2]interface{}{
|
||||||
|
false,
|
||||||
|
fmt.Sprintf("ERR %s", err.Error()),
|
||||||
|
}
|
||||||
|
w.genericWrite(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeStatus(status string) {
|
||||||
|
w.genericWrite(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeInteger(n int64) {
|
||||||
|
w.genericWrite(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeBulk(b []byte) {
|
||||||
|
if b == nil {
|
||||||
|
w.genericWrite(nil)
|
||||||
|
} else {
|
||||||
|
w.genericWrite(ledis.String(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeArray(lst []interface{}) {
|
||||||
|
w.genericWrite(lst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeSliceArray(lst [][]byte) {
|
||||||
|
arr := make([]interface{}, len(lst))
|
||||||
|
for i, elem := range lst {
|
||||||
|
if elem == nil {
|
||||||
|
arr[i] = nil
|
||||||
|
} else {
|
||||||
|
arr[i] = ledis.String(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.genericWrite(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeFVPairArray(lst []ledis.FVPair) {
|
||||||
|
m := make(map[string]string)
|
||||||
|
for _, elem := range lst {
|
||||||
|
m[ledis.String(elem.Field)] = ledis.String(elem.Value)
|
||||||
|
}
|
||||||
|
w.genericWrite(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeScorePairArray(lst []ledis.ScorePair, withScores bool) {
|
||||||
|
var arr []string
|
||||||
|
if withScores {
|
||||||
|
arr = make([]string, 2*len(lst))
|
||||||
|
for i, data := range lst {
|
||||||
|
arr[2*i] = ledis.String(data.Member)
|
||||||
|
arr[2*i+1] = strconv.FormatInt(data.Score, 10)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
arr = make([]string, len(lst))
|
||||||
|
for i, data := range lst {
|
||||||
|
arr[i] = ledis.String(data.Member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.genericWrite(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) writeBulkFrom(n int64, rb io.Reader) {
|
||||||
|
w.writeError(fmt.Errorf("unsuport"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *httpWriter) flush() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeJSON(resutl interface{}, w http.ResponseWriter) {
|
||||||
|
buf, err := json.Marshal(resutl)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-type", "application/json; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
|
||||||
|
|
||||||
|
_, err = w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBSON(result interface{}, w http.ResponseWriter) {
|
||||||
|
buf, err := bson.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-type", "application/octet-stream")
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
|
||||||
|
|
||||||
|
_, err = w.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMsgPack(result interface{}, w http.ResponseWriter) {
|
||||||
|
w.Header().Set("Content-type", "application/octet-stream")
|
||||||
|
|
||||||
|
var mh codec.MsgpackHandle
|
||||||
|
enc := codec.NewEncoder(w, &mh)
|
||||||
|
if err := enc.Encode(result); err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,6 @@ type respClient struct {
|
||||||
rb *bufio.Reader
|
rb *bufio.Reader
|
||||||
|
|
||||||
req *requestContext
|
req *requestContext
|
||||||
hdl *requestHandler
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type respWriter struct {
|
type respWriter struct {
|
||||||
|
@ -44,8 +43,6 @@ func newClientRESP(conn net.Conn, app *App) {
|
||||||
c.req.resp = newWriterRESP(conn)
|
c.req.resp = newWriterRESP(conn)
|
||||||
c.req.remoteAddr = conn.RemoteAddr().String()
|
c.req.remoteAddr = conn.RemoteAddr().String()
|
||||||
|
|
||||||
c.hdl = newRequestHandler(app)
|
|
||||||
|
|
||||||
go c.run()
|
go c.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +141,7 @@ func (c *respClient) handleRequest(reqData [][]byte) {
|
||||||
|
|
||||||
req.db = c.db
|
req.db = c.db
|
||||||
|
|
||||||
c.hdl.handle(req)
|
c.req.perform()
|
||||||
|
|
||||||
c.db = req.db // "SELECT"
|
c.db = req.db // "SELECT"
|
||||||
|
|
||||||
|
|
|
@ -33,10 +33,6 @@ type requestContext struct {
|
||||||
|
|
||||||
syncBuf bytes.Buffer
|
syncBuf bytes.Buffer
|
||||||
compressBuf []byte
|
compressBuf []byte
|
||||||
}
|
|
||||||
|
|
||||||
type requestHandler struct {
|
|
||||||
app *App
|
|
||||||
|
|
||||||
reqErr chan error
|
reqErr chan error
|
||||||
|
|
||||||
|
@ -51,20 +47,12 @@ func newRequestContext(app *App) *requestContext {
|
||||||
req.db, _ = app.ldb.Select(0) //use default db
|
req.db, _ = app.ldb.Select(0) //use default db
|
||||||
|
|
||||||
req.compressBuf = make([]byte, 256)
|
req.compressBuf = make([]byte, 256)
|
||||||
|
req.reqErr = make(chan error)
|
||||||
|
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRequestHandler(app *App) *requestHandler {
|
func (req *requestContext) perform() {
|
||||||
hdl := new(requestHandler)
|
|
||||||
|
|
||||||
hdl.app = app
|
|
||||||
hdl.reqErr = make(chan error)
|
|
||||||
|
|
||||||
return hdl
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *requestHandler) handle(req *requestContext) {
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
@ -75,26 +63,30 @@ func (h *requestHandler) handle(req *requestContext) {
|
||||||
err = ErrNotFound
|
err = ErrNotFound
|
||||||
} else {
|
} else {
|
||||||
go func() {
|
go func() {
|
||||||
h.reqErr <- exeCmd(req)
|
req.reqErr <- exeCmd(req)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
err = <-h.reqErr
|
err = <-req.reqErr
|
||||||
}
|
}
|
||||||
|
|
||||||
duration := time.Since(start)
|
duration := time.Since(start)
|
||||||
|
|
||||||
if h.app.access != nil {
|
if req.app.access != nil {
|
||||||
fullCmd := h.catGenericCommand(req)
|
fullCmd := req.catGenericCommand()
|
||||||
cost := duration.Nanoseconds() / 1000000
|
cost := duration.Nanoseconds() / 1000000
|
||||||
|
|
||||||
h.app.access.Log(req.remoteAddr, cost, fullCmd[:256], err)
|
truncateLen := len(fullCmd)
|
||||||
|
if truncateLen > 256 {
|
||||||
|
truncateLen = 256
|
||||||
|
}
|
||||||
|
|
||||||
|
req.app.access.Log(req.remoteAddr, cost, fullCmd[:truncateLen], err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.resp.writeError(err)
|
req.resp.writeError(err)
|
||||||
}
|
}
|
||||||
req.resp.flush()
|
req.resp.flush()
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,8 +101,8 @@ func (h *requestHandler) handle(req *requestContext) {
|
||||||
// return h.catGenericCommand(req)
|
// return h.catGenericCommand(req)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
func (h *requestHandler) catGenericCommand(req *requestContext) []byte {
|
func (req *requestContext) catGenericCommand() []byte {
|
||||||
buffer := h.buf
|
buffer := req.buf
|
||||||
buffer.Reset()
|
buffer.Reset()
|
||||||
|
|
||||||
buffer.Write([]byte(req.cmd))
|
buffer.Write([]byte(req.cmd))
|
||||||
|
|
Loading…
Reference in New Issue