Created
February 27, 2012 02:01
-
-
Save bgentry/1920703 to your computer and use it in GitHub Desktop.
A simple SPDY server
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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