diff --git a/.gitignore b/.gitignore index 1ca9571..e90b07d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ npm-debug.log +.vimrc diff --git a/README.md b/README.md index 99a916c..9293643 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ SPDY/HTTP2 generic transport implementation. ## Usage +### server + ```javascript var transport = require('spdy-transport'); @@ -17,33 +19,99 @@ var transport = require('spdy-transport'); // of `net.createServer`'s connection handler. var server = transport.connection.create(socket, { - protocol: 'http2', + protocol: 'http2', // or 'spdy' isServer: true }); server.on('stream', function(stream) { console.log(stream.method, stream.path, stream.headers); + + // necessary for the stream to be established in both ends stream.respond(200, { header: 'value' }); - stream.on('readable', function() { - var chunk = stream.read(); - if (!chunk) - return; + // response body + stream.write(); - console.log(chunk); - }); + stream.end() // sends FLAG_FIN, setting the connection half open + + var chunk = stream.read(); stream.on('end', function() { console.log('end'); }); + + // And other node.js Stream APIs + // ... ref: https://nodejs.org/api/stream.html +}); +``` + +### client + +```javascript +var transport = require('spdy-transport'); + +// NOTE: socket is some stream or net.Socket instance, may be an argument +// of `net.createServer`'s connection handler. + +var client = transport.connection.create(socket, { + protocol: 'http2', + isServer: false +}); + +// client.start(), 4 for http2, [2,3,3.1] for spdy 2.0, 3.0 and 3.1 respectively +client.start(4); + +client.request({ + method: 'GET', + host: 'localhost', + path: '/', + headers: { + a: 'b', + c: 'd' + } + }, function (err, stream) { + if (err) { + return console.log(err); + } + + stream.on('response', function (code, headers) { + console.log(code, headers); + + // request body + stream.write(); + + }) + + var chunk = stream.read(); + // And other node.js Stream APIs - // ... + // ... ref: https://nodejs.org/api/stream.html }); ``` +### frame listener + +```javascript +// either client or server +connection.on('frame', function (frame) { + console.log(frame.type); +}); +``` + +## Implementation notes + +- spdy-transport always sends a SETTINGS frame as the first frame once the socket is open +- the server requires a http `":method"` and `":path"` header. If they are not added on the client, `"GET"` and `"/"` are added by default + +## Interop + +`spdy-transport` is capable of interoping with other spdy framing layer implementations such as: + +- [spdystream](https://github.com/docker/spdystream), [example](/examples/spdystream-interop) + ## LICENSE This software is licensed under the MIT License. diff --git a/examples/spdystream-interop/client.go b/examples/spdystream-interop/client.go new file mode 100644 index 0000000..1fe3c60 --- /dev/null +++ b/examples/spdystream-interop/client.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "github.com/docker/spdystream" + "net" + "net/http" +) + +func main() { + conn, err := net.Dial("tcp", "localhost:9090") + if err != nil { + panic(err) + } + spdyConn, err := spdystream.NewConnection(conn, false) + if err != nil { + panic(err) + } + + header := http.Header{} + // Although spdystream doesn't mind about :method and :path headers, these + // are necessary for interop with spdy-transport + header.Add(":method", "GET") + header.Add(":path", "/") + + go spdyConn.Serve(spdystream.NoOpStreamHandler) + + stream, err := spdyConn.CreateStream(header, nil, false) + if err != nil { + panic(err) + } + + stream.Wait() + + fmt.Fprint(stream, "Hey, how is it going! (go client asking)") + + buf := make([]byte, 35) + stream.Read(buf) + fmt.Println(string(buf)) + + stream.Close() +} diff --git a/examples/spdystream-interop/client.js b/examples/spdystream-interop/client.js new file mode 100644 index 0000000..f0eb367 --- /dev/null +++ b/examples/spdystream-interop/client.js @@ -0,0 +1,42 @@ +var tcp = require('net') +var transport = require('spdy-transport') + +var socket = tcp.connect({port: 9090}, function () { + + var client = transport.connection.create(socket, { + protocol: 'spdy', + isServer: false + }) + + client.on('frame', function (frame) { + console.log(frame.type) + }) + + client.start(3.1) + + client.request({ + method: 'GET', + host: 'localhost', + path: '/', + headers: { + a: 'b' + }}, function (err, stream) { + console.log('stream established') + if (err) { + return console.log(err) + } + stream.write('Hey, how is it going. (node client asking)!') + + stream.on('readable', function () { + var chunk = stream.read() + if (!chunk) { + return + } + console.log(chunk.toString()) + }) + + stream.on('response', function (code, headers) { + console.log(code, headers) + }) + }) +}) diff --git a/examples/spdystream-interop/server.go b/examples/spdystream-interop/server.go new file mode 100644 index 0000000..abc581e --- /dev/null +++ b/examples/spdystream-interop/server.go @@ -0,0 +1,26 @@ +package main + +import ( + "github.com/docker/spdystream" + "net" +) + +func main() { + listener, err := net.Listen("tcp", "localhost:9090") + if err != nil { + panic(err) + } + + for { + conn, err := listener.Accept() + if err != nil { + panic(err) + } + spdyConn, err := spdystream.NewConnection(conn, true) + if err != nil { + panic(err) + } + // This is an "echo server" example, known in the go world as "mirror" + go spdyConn.Serve(spdystream.MirrorStreamHandler) + } +} diff --git a/examples/spdystream-interop/server.js b/examples/spdystream-interop/server.js new file mode 100644 index 0000000..7e371df --- /dev/null +++ b/examples/spdystream-interop/server.js @@ -0,0 +1,32 @@ +var tcp = require('net') +var transport = require('spdy-transport') + +tcp.createServer(function (socket) { + var server = transport.connection.create(socket, { + protocol: 'spdy', + isServer: true + }) + + server.on('stream', function (stream) { + console.log(stream.method, stream.path, stream.headers) + + stream.respond(200, { + c: 'd' + }) + stream.write('Hey, how is it going? (node server asking') + + stream.on('readable', function () { + var chunk = stream.read() + if (!chunk) { + return + } + console.log(chunk.toString()) + }) + + stream.on('end', function () { + console.log('end') + }) + }) + +}).listen(9090) +