Skip to content

Instantly share code, notes, and snippets.

@bgentry
Created February 27, 2012 02:01
Show Gist options
  • Save bgentry/1920703 to your computer and use it in GitHub Desktop.
Save bgentry/1920703 to your computer and use it in GitHub Desktop.
A simple SPDY server
// Based heavily on https://bitbucket.org/zombiezen/spdy, adapted for
// recent Go releases.
package main
import (
"crypto/tls"
"fmt"
"go.net/spdy"
"io"
"net"
"net/http"
"net/url"
"reflect"
"strconv"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "HERRO PREASE")
})
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
})
err := ListenAndServeTLS("0.0.0.0:4430", "./cert.pem", "./key.pem", nil)
fmt.Printf("Err: %v\n", err)
fmt.Println("Exiting main()")
}
// ListenAndServeTLS acts like ListenAndServe except it uses TLS.
func ListenAndServeTLS(addr string, certFile, keyFile string, handler http.Handler) (err error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic(fmt.Errorf("Error loading keypair: %v", err))
}
config := &tls.Config{
NextProtos: []string{"spdy/2"},
Certificates: []tls.Certificate{cert},
CipherSuites: []uint16{tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA},
}
config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return
}
conn, err := net.Listen("tcp", addr)
if err != nil {
return
}
tlsListener := tls.NewListener(conn, config)
return (&Server{addr, handler}).Serve(tlsListener)
}
// A Server handles incoming SPDY connections with HTTP handlers.
type Server struct {
Addr string
Handler http.Handler
}
// ListenAndServe services SPDY requests using the given listener.
// If the handler is nil, then http.DefaultServeMux is used.
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
handler := srv.Handler
if handler == nil {
handler = http.DefaultServeMux
}
for {
c, err := l.Accept()
if err != nil {
return err
}
s, err := newSession(c, handler)
if err != nil {
return err
}
go s.serve()
}
return nil
}
// A session manages a single TCP connection to a client.
type session struct {
c net.Conn
handler http.Handler
in, out chan spdy.Frame
streams map[uint32]*serverStream // all access is done synchronously
framer *spdy.Framer
}
func newSession(c net.Conn, h http.Handler) (s *session, err error) {
f, err := spdy.NewFramer(c, c)
if err != nil {
panic(fmt.Errorf("Framer Error: %v", err))
}
s = &session{
c: c,
handler: h,
framer: f,
in: make(chan spdy.Frame),
out: make(chan spdy.Frame),
streams: make(map[uint32]*serverStream),
}
return
}
func (sess *session) serve() {
defer sess.c.Close()
go sess.sendFrames()
go sess.receiveFrames()
for f := range sess.in {
switch frame := f.(type) {
case *spdy.SynStreamFrame:
if stream, err := newServerStream(sess, *frame); err == nil {
if _, exists := sess.streams[stream.id]; !exists {
sess.streams[stream.id] = stream
go func() {
sess.handler.ServeHTTP(stream, stream.Request())
stream.finish()
}()
}
}
case *spdy.PingFrame:
time.Sleep(1e3)
sess.out <- frame
default:
t := reflect.TypeOf(frame)
fmt.Printf("Unhandled Frame type: %v -- %v\n", t.Name(), t)
}
}
}
func (sess *session) sendFrames() {
for frame := range sess.out {
if err := sess.framer.WriteFrame(frame); err != nil {
t := reflect.TypeOf(frame)
panic(fmt.Errorf("Error writing Frame %v -- %v -- %v\n", t.Name(), t, err))
}
}
}
func (sess *session) receiveFrames() {
defer close(sess.in)
for {
frame, err := sess.framer.ReadFrame()
if err != nil {
switch err {
case io.EOF:
fmt.Println("io.EOF in receiveFrames()")
return
default:
panic(fmt.Errorf("Error handling incoming frame: %v -- %v\n", err, frame))
}
}
t := reflect.TypeOf(frame)
fmt.Printf("Handled incoming frame: %v -- %v\n", t.Name(), t)
sess.in <- frame
}
}
// A serverStream is a logical data stream inside a session. A serverStream
// services a single request.
type serverStream struct {
id uint32
session *session
closed bool
requestHeaders http.Header
responseHeaders http.Header
wroteHeader bool
// receivedData
}
func newServerStream(sess *session, frame spdy.SynStreamFrame) (st *serverStream, err error) {
st = &serverStream{
session: sess,
responseHeaders: make(http.Header),
}
if frame.CFHeader.Flags&spdy.ControlFlagFin == 0 {
// Request body will follow
// st.dataPipe = apipe()
// TODO: Ignore request data for now
}
st.id = frame.StreamId
st.requestHeaders = frame.Headers
return
}
// Request returns the request data associated with the serverStream.
func (st *serverStream) Request() (req *http.Request) {
// TODO: Add more info
u, _ := url.Parse(st.requestHeaders.Get("url"))
req = &http.Request{
Method: st.requestHeaders.Get("method"),
URL: u,
Proto: st.requestHeaders.Get("version"),
Header: st.requestHeaders,
Body: st,
RemoteAddr: st.session.c.RemoteAddr().String(),
}
fmt.Printf("Request URL: %v\n", req.URL)
return
}
// Header returns the current response headers.
func (st *serverStream) Header() http.Header { return st.responseHeaders }
func (st *serverStream) Write(p []byte) (n int, err error) {
if st.closed {
err = fmt.Errorf("Write on closed serverStream")
return
}
if !st.wroteHeader {
st.WriteHeader(http.StatusOK)
}
for len(p) > 0 {
frame := spdy.DataFrame{
StreamId: st.id,
}
if len(p) < spdy.MaxDataLength {
frame.Data = make([]byte, len(p))
} else {
frame.Data = make([]byte, spdy.MaxDataLength)
}
copy(frame.Data, p)
p = p[len(frame.Data):]
st.session.out <- &frame
n += len(frame.Data)
}
return
}
func (st *serverStream) Read(p []byte) (n int, err error) {
// TODO: return actual request data
return 0, io.EOF
// return st.dataPipe.read(p)
}
func (st *serverStream) WriteHeader(code int) {
if st.wroteHeader {
return
}
st.responseHeaders.Set("status", strconv.Itoa(code)+" "+http.StatusText(code))
st.responseHeaders.Set("version", "HTTP/1.1")
if st.responseHeaders.Get("Content-Type") == "" {
st.responseHeaders.Set("Content-Type", "text/html; charset=utf-8")
}
if st.responseHeaders.Get("Date") == "" {
st.responseHeaders.Set("Date", time.Now().UTC().Format(http.TimeFormat))
}
// Write the frame
// TODO: Copy headers
st.session.out <- &spdy.SynReplyFrame{
StreamId: st.id,
Headers: st.responseHeaders,
}
st.wroteHeader = true
}
// Close sends a closing frame, thus preventing the server from sending more
// data over the stream. The client may still send data.
func (st *serverStream) Close() (err error) {
if st.closed {
return
}
st.session.out <- &spdy.DataFrame{
StreamId: st.id,
Flags: spdy.DataFlagFin,
Data: []byte{},
}
st.closed = true
return nil
}
func (st *serverStream) finish() (err error) {
if !st.wroteHeader {
st.WriteHeader(http.StatusOK)
}
return st.Close()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment