Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some extra documentation and a go-node interop example #5

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
npm-debug.log
.vimrc
80 changes: 79 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,32 @@ SPDY/HTTP2 generic transport implementation.

## Usage

### server

```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 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'
});

// response body
stream.write(<data>)

stream.end() // sends FLAG_FIN, setting the connection half open

stream.on('readable', function() {
var chunk = stream.read();
if (!chunk)
Expand All @@ -44,6 +53,75 @@ server.on('stream', function(stream) {
});
```

### 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
})

// optional for http2, however mandatory for spdy ([2, 3, 3.1])
client.start(4)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that it is actually that optional. ;)


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(<data>)

// And other node.js Stream APIs
// ...
})

stream.on('readable', function () {
var chunk = stream.read()
if (!chunk) {
return
}
console.log(chunk.toString())
})
})
```

### frame listener
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is kind of internal thing, just for testing. Are you sure that it is worth putting it into readme?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, so how should we correctly read the body sent by the server?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use stream.read()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Although I believe it might be a nice reminder that a good way to check if more bytes are available is through the .on('readable') event. Have seen a couple of times folks doing something like:

var buf = stream.read()
if (!buf) {
  return setTimeout(tryAgainInAbit, 1e3)
}

What if I left only the stream.read() and a comment with a note about the .on('readable') event?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm... I'm not sure if this library is the right place to educate people on how the streams2 API should be used. I'd just link the node documentation if you think that this question is going to be raised often.


```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.
Expand Down
42 changes: 42 additions & 0 deletions examples/spdystream-interop/client.go
Original file line number Diff line number Diff line change
@@ -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()
}
42 changes: 42 additions & 0 deletions examples/spdystream-interop/client.js
Original file line number Diff line number Diff line change
@@ -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)
})
})
})
26 changes: 26 additions & 0 deletions examples/spdystream-interop/server.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
32 changes: 32 additions & 0 deletions examples/spdystream-interop/server.js
Original file line number Diff line number Diff line change
@@ -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)