Skip to content

Commit

Permalink
updated parser
Browse files Browse the repository at this point in the history
  • Loading branch information
tidwall committed Nov 2, 2017
1 parent ae8de20 commit 23d55c2
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 58 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ config := &tls.Config{Certificates: []tls.Certificate{cer}}
// wrap the events with a TLS translator

events = evio.Translate(events, nil,
func(rw io.ReadWriter) io.ReadWriter {
func(id int, rw io.ReadWriter) io.ReadWriter {
return tls.Server(evio.NopConn(rw), config)
},
)
Expand Down Expand Up @@ -192,7 +192,7 @@ Please check out the [examples](examples) subdirectory for a simplified [redis](

To run an example:

```bash
```sh
$ go run examples/http-server/main.go
$ go run examples/redis-server/main.go
$ go run examples/echo-server/main.go
Expand All @@ -204,9 +204,10 @@ The benchmarks below use pipelining which allows for combining multiple Redis co

**Real Redis**

```
```sh
$ redis-server
```

```
redis-benchmark -p 6379 -t ping,set,get -q -P 32
PING_INLINE: 869565.19 requests per second
Expand All @@ -217,9 +218,10 @@ GET: 1265822.75 requests per second

**Redis clone (evio)**

```
```sh
$ go run examples/redis-server/main.go
```

```
redis-benchmark -p 6380 -t ping,set,get -q -P 32
PING_INLINE: 2380952.50 requests per second
Expand Down
21 changes: 10 additions & 11 deletions evio_translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,18 @@ func NopConn(rw io.ReadWriter) net.Conn {
return &nopConn{rw}
}

// Translate provides a utility for performing byte level translation
// on the input and output streams for a connection. This is useful for
// things like compression, encryption, TLS, etc. The function wraps
// existing events and returns new events that manage the translation.
// The `should` parameter is an optional function that can be used to
// ignore or accept the translation for a specific connection.
// The `translate` parameter is a function that provides a ReadWriter
// for each new connection and returns a ReadWriter that performs the
// actual translation.
// Translate provides a utility for performing byte level translation on the
// input and output streams for a connection. This is useful for things like
// compression, encryption, TLS, etc. The function wraps existing events and
// returns new events that manage the translation. The `should` parameter is
// an optional function that can be used to ignore or accept the translation
// for a specific connection. The `translate` parameter is a function that
// provides a ReadWriter for each new connection and returns a ReadWriter
// that performs the actual translation.
func Translate(
events Events,
should func(id int, addr Addr) bool,
translate func(rd io.ReadWriter) io.ReadWriter,
translate func(id int, rd io.ReadWriter) io.ReadWriter,
) Events {
tevents := events
var wake func(id int) bool
Expand All @@ -94,7 +93,7 @@ func Translate(
}
idc[id] = c
mu.Unlock()
tc := translate(c)
tc := translate(id, c)
for st := 0; st < 2; st++ {
c.rd[st], c.wr[st] = io.Pipe()
var rd io.Reader
Expand Down
107 changes: 70 additions & 37 deletions examples/http-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/tidwall/evio"
)

var res string

type request struct {
proto, method string
path, query string
Expand All @@ -34,12 +36,20 @@ func main() {
var port int
var tlsport int
var tlspem string
var aaaa bool

flag.IntVar(&port, "port", 8080, "server port")
flag.IntVar(&tlsport, "tlsport", 4443, "tls port")
flag.StringVar(&tlspem, "tlscert", "", "tls pem cert/key file")
flag.BoolVar(&aaaa, "aaaa", false, "aaaaa....")
flag.Parse()

if aaaa {
res = strings.Repeat("a", 1024)
} else {
res = "Hello World!\r\n"
}

var events evio.Events
var conns = make(map[int]*conn)

Expand All @@ -65,6 +75,16 @@ func main() {
}

events.Data = func(id int, in []byte) (out []byte, action evio.Action) {
// out = []byte(`HTTP/1.1 200 OK
// Server: evio
// Date: Thu, 02 Nov 2017 15:48:57 GMT
// Content-Type: text/plain; charset=utf-8
// Content-Length: 1024

// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa`)

// return

if in == nil {
return
}
Expand Down Expand Up @@ -92,25 +112,24 @@ func main() {
return
}
// We at least want the single http address.
addrs := []string{fmt.Sprintf("tcp://:%d", port)}
addrs := []string{fmt.Sprintf("tcp://localhost:%d", port)}
if tlspem != "" {

// load the cert and key pair from the concat'd pem file.
cer, err := tls.LoadX509KeyPair(tlspem, tlspem)
if err != nil {
log.Fatal(err)
}
config := &tls.Config{Certificates: []tls.Certificate{cer}}
// Update the address list to include https.
addrs = append(addrs, fmt.Sprintf("tcp://:%d", tlsport))
addrs = append(addrs, fmt.Sprintf("tcp://localhost:%d", tlsport))

// TLS translate the events
events = evio.Translate(events,
func(id int, addr evio.Addr) bool {
// only translate for the second address.
return addr.Index == 1
},
func(rw io.ReadWriter) io.ReadWriter {
func(id int, rw io.ReadWriter) io.ReadWriter {
// Use the standard Go crypto/tls package and create a tls.Conn
// from the provided io.ReadWriter. Here we use the handy
// evio.NopConn utility to create a barebone net.Conn in order
Expand All @@ -126,7 +145,7 @@ func main() {
// appendhandle handles the incoming request and appends the response to
// the provided bytes, which is then returned to the caller.
func appendhandle(b []byte, req *request) []byte {
return appendresp(b, "200 OK", "", "Hello World!\n")
return appendresp(b, "200 OK", "", res)
}

// appendresp will append a valid http response to the provide bytes.
Expand All @@ -137,6 +156,7 @@ func appendresp(b []byte, status, head, body string) []byte {
b = append(b, ' ')
b = append(b, status...)
b = append(b, '\r', '\n')
b = append(b, "Server: evio\r\n"...)
b = append(b, "Date: "...)
b = time.Now().AppendFormat(b, "Mon, 02 Jan 2006 15:04:05 GMT")
b = append(b, '\r', '\n')
Expand All @@ -157,51 +177,64 @@ func appendresp(b []byte, status, head, body string) []byte {
// waits for the entire payload to be buffered before returning a
// valid request.
func parsereq(data []byte, req *request) (leftover []byte, err error) {
var s int
var n int
sdata := string(data)
var i, s int
var top string
var clen int
var i int
for ; i < len(data); i++ {
if i > 1 && data[i] == '\n' && data[i-1] == '\r' {
line := string(data[s : i-1])
s = i + 1
if n == 0 {
top = line
parts := strings.Split(top, " ")
if len(parts) != 3 {
return data, fmt.Errorf("malformed request '%s'", top)
}
req.method = parts[0]
req.path = parts[1]
req.proto = parts[2]
for i := 0; i < len(req.path); i++ {
if req.path[i] == '?' {
req.query = req.path[i+1:]
req.path = req.path[:i]
break
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
}
} else if line == "" {
req.head = string(data[len(top)+2 : i+1])
}
break
}
}
if req.proto == "" {
return data, fmt.Errorf("malformed request")
}
top = 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(top)+2 : i+1]
i++
if clen > 0 {
if len(data[i:]) < clen {
if len(sdata[i:]) < clen {
break
}
req.body = string(data[i : i+clen])
req.body = sdata[i : i+clen]
i += clen
}
return data[i:], nil
} else {
if strings.HasPrefix(line, "Content-Length:") {
n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64)
if err == nil {
clen = int(n)
}
}
if strings.HasPrefix(line, "Content-Length:") {
n, err := strconv.ParseInt(strings.TrimSpace(line[len("Content-Length:"):]), 10, 64)
if err == nil {
clen = int(n)
}
}
n++
}
}
// not enough data
Expand Down
23 changes: 17 additions & 6 deletions examples/redis-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package main

import (
"flag"
"fmt"
"log"
"net"
"strings"
Expand All @@ -19,33 +21,38 @@ type conn struct {
}

func main() {
var port int
var unixsocket string
flag.IntVar(&port, "port", 6380, "server port")
flag.StringVar(&unixsocket, "unixsocket", "socket", "unix socket")
flag.Parse()

var conns = make(map[int]*conn)
var keys = make(map[string]string)
var events evio.Events
events.Serving = func(wake func(id int) bool, addrs []net.Addr) (action evio.Action) {
log.Printf("serving at tcp port 6380")
log.Printf("serving on unix socket")
log.Printf("redis server started on port %d", port)
if unixsocket != "" {
log.Printf("redis server started at %s", unixsocket)
}
return
}
events.Opened = func(id int, addr evio.Addr) (out []byte, opts evio.Options, action evio.Action) {
conns[id] = &conn{}
return
}

events.Closed = func(id int, err error) (action evio.Action) {
delete(conns, id)
return
}
events.Data = func(id int, in []byte) (out []byte, action evio.Action) {

c := conns[id]
data := c.is.Begin(in)
var n int
var complete bool
var err error
var args [][]byte
for action == evio.None {

complete, args, _, data, err = redcon.ReadNextCommand(data, args[:0])
if err != nil {
action = evio.Close
Expand Down Expand Up @@ -123,7 +130,11 @@ func main() {
c.is.End(data)
return
}
err := evio.Serve(events, "tcp://0.0.0.0:6380", "unix://socket")
addrs := []string{fmt.Sprintf("tcp://0.0.0.0:%d", port)}
if unixsocket != "" {
addrs = append(addrs, fmt.Sprintf("unix://%s", unixsocket))
}
err := evio.Serve(events, addrs...)
if err != nil {
log.Fatal(err)
}
Expand Down

0 comments on commit 23d55c2

Please sign in to comment.