├── docs ├── img │ ├── spdy-arch.png │ └── end-to-end-http.png └── specs │ └── Specsv1.1.pdf ├── cert ├── README.md ├── makecert.sh ├── clientTLS │ ├── client.key │ └── client.pem └── serverTLS │ ├── server.key │ └── server.pem ├── .gitignore ├── examples ├── server │ └── spdyserver.go ├── serverTLS │ ├── tlsserver.go │ ├── server.key │ └── server.pem ├── client │ └── spdyclient.go └── clientTLS │ ├── client.key │ ├── tlsclient.go │ └── client.pem ├── .travis.yml ├── proxy.go ├── debug.go ├── misc.go ├── doc.go ├── client_conn.go ├── server_test.go ├── types.go ├── spdy_test.go ├── server_conn.go ├── LICENSE ├── README.md ├── frame.go ├── header.go ├── session.go └── stream.go /docs/img/spdy-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amahi/spdy/HEAD/docs/img/spdy-arch.png -------------------------------------------------------------------------------- /docs/specs/Specsv1.1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amahi/spdy/HEAD/docs/specs/Specsv1.1.pdf -------------------------------------------------------------------------------- /docs/img/end-to-end-http.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amahi/spdy/HEAD/docs/img/end-to-end-http.png -------------------------------------------------------------------------------- /cert/README.md: -------------------------------------------------------------------------------- 1 | TLS Configuration 2 | ======== 3 | Use [makecert.sh](makecert.sh) to create client and server certificate and keys. call this script with an email address (valid or not) like: 4 | 5 | ``` 6 | ./makecert.sh abc@random.com 7 | ``` 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /examples/server/spdyserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/amahi/spdy" 6 | "net/http" 7 | ) 8 | 9 | func handler(w http.ResponseWriter, r *http.Request) { 10 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 11 | } 12 | 13 | func main() { 14 | http.HandleFunc("/", handler) 15 | err := spdy.ListenAndServe("localhost:4040",nil) 16 | if err != nil { 17 | fmt.Println(err) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - 1.5 8 | 9 | before_install: 10 | - go get code.google.com/p/go.tools/cmd/cover || go get golang.org/x/tools/cmd/cover 11 | - go get github.com/axw/gocov/gocov 12 | - go get github.com/mattn/goveralls 13 | 14 | script: 15 | - go test -v -covermode=count -coverprofile=coverage.out 16 | - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken woItNGBMB0m954phEISoUSa4qgNDTD7D2 17 | -------------------------------------------------------------------------------- /examples/serverTLS/tlsserver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/amahi/spdy" 6 | "net/http" 7 | ) 8 | 9 | func handler(w http.ResponseWriter, r *http.Request) { 10 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 11 | } 12 | 13 | func main() { 14 | spdy.EnableDebug() 15 | http.HandleFunc("/", handler) 16 | err := spdy.ListenAndServeTLS("localhost:4040", "server.pem", "server.key" , nil) 17 | if err != nil { 18 | fmt.Println(err) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cert/makecert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # call this script with an email address (valid or not). 3 | # like: 4 | # ./makecert.sh joe@random.com 5 | mkdir serverTLS 6 | rm serverTLS/* 7 | mkdir clientTLS 8 | rm clientTLS/* 9 | echo "make server cert" 10 | openssl req -new -nodes -x509 -out serverTLS/server.pem -keyout serverTLS/server.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1" 11 | echo "make client cert" 12 | openssl req -new -nodes -x509 -out clientTLS/client.pem -keyout clientTLS/client.key -days 3650 -subj "/C=DE/ST=NRW/L=Earth/O=Random Company/OU=IT/CN=www.random.com/emailAddress=$1" 13 | -------------------------------------------------------------------------------- /examples/client/spdyclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/amahi/spdy" 6 | "io" 7 | "net/http" 8 | ) 9 | 10 | func main() { 11 | //make a spdy client with a given address 12 | client, err := spdy.NewClient("localhost:4040") 13 | if err != nil { 14 | //handle error here 15 | } 16 | 17 | //make a request 18 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 19 | if err != nil { 20 | //handle error here 21 | } 22 | 23 | //now send the request to obtain a http response 24 | res, err := client.Do(req) 25 | if err != nil { 26 | //something went wrong 27 | } 28 | 29 | //now handle the response 30 | data := make([]byte, int(res.ContentLength)) 31 | _, err = res.Body.(io.Reader).Read(data) 32 | fmt.Println(string(data)) 33 | fmt.Println(res.Header) 34 | res.Body.Close() 35 | } 36 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Proxy function 6 | 7 | package spdy 8 | 9 | import ( 10 | "net/http" 11 | ) 12 | 13 | // NewStreamProxy starts a new stream and proxies the given HTTP Request to 14 | // it, writing the response to the given ResponseWriter. If there is an error, 15 | // it will be returned, but the ResponseWriter will get a 404 Not Found. 16 | func (s *Session) NewStreamProxy(r *http.Request, w http.ResponseWriter) (err error) { 17 | 18 | str := s.NewClientStream() 19 | if str == nil { 20 | log.Println("ERROR in NewClientStream: cannot create stream") 21 | http.NotFound(w, r) 22 | return 23 | } 24 | err = str.Request(r, w) 25 | if err != nil { 26 | http.NotFound(w, r) 27 | log.Println("ERROR in Request:", err) 28 | return 29 | } 30 | 31 | return 32 | } 33 | -------------------------------------------------------------------------------- /debug.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Debug and logging related functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "io" 11 | "io/ioutil" 12 | logging "log" 13 | "os" 14 | ) 15 | 16 | // regular app logging - enabled by default 17 | var log = logging.New(os.Stderr, "[SPDY] ", logging.LstdFlags|logging.Lshortfile) 18 | 19 | // app logging for the purposes of debugging - disabled by default 20 | var debug = logging.New(ioutil.Discard, "[SPDY DEBUG] ", logging.LstdFlags) 21 | 22 | // EnableDebug turns on the output of debugging messages to Stdout 23 | func EnableDebug() { 24 | debug = logging.New(os.Stdout, "[SPDY DEBUG] ", logging.LstdFlags) 25 | } 26 | 27 | // SetLog sets the output of logging to a given io.Writer 28 | func SetLog(w io.Writer) { 29 | log = logging.New(w, "[SPDY] ", logging.LstdFlags|logging.Lshortfile) 30 | } 31 | -------------------------------------------------------------------------------- /cert/clientTLS/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALjPz45dMHkYN3mI 3 | 6muzrRuZmp4GPKmcrgCCrVKwa3RanDQN6RHmnhICqSjt622lKbRmtFGc+EkBjeL0 4 | GjwIVDL0sM5L9lRNXPFCbowcpmzUt7CWjacap2htznPyzE34gcK1cKdLXP/Wp2MO 5 | 5TPN6JDCb1X+oevAbPg9YQo+KGEDAgMBAAECgYBEEXY2zyftUfgMsVr19CWMBtUl 6 | 4tIw/l4wHfFMhWHuZvklSwJO74vEwmofgxG1PqHPqgw8Cv5/lSz3LEUFW2P3B6V2 7 | w403eruXO0JYYPRFq3pCegbGCWiQUgbQq2HZE8zAhbapdwPA370BZaIiukTIv2Na 8 | 7Yst1XXxKCzKahDnwQJBAO9rgnxtd0/lQsEepSFGOr5uekTKnZbSyabWw7skjiOB 9 | 76TaoMbMSuhoUeJNZRe4KDktSsDJesNpA9ACZCJYlfMCQQDFnDHbwKjPiJZmZj3f 10 | Al8D8Fu2RO9WlJCUnvQtMUzZVAe6SR8rOzR1cuq2EZ7awz5uBYDZXbgPk+jogJSv 11 | XXyxAkAEMaHrhA9e+ukccOj7mDtXMqA5rELubWrS3Vg4Yv+KZ7BG9v1x/goh4BHv 12 | IOPwcm1h8wUoM19PJPvIHQKcB2k5AkEAs/0x2JQh7m3dd8WWg0LYonDSSDF/wl+t 13 | VQF3LJakBaupIj8p+amCfZu/NzjTdTSy72lp1BKt7NpvIR7Ezvoa8QJBAKFYYo8b 14 | MgE9w0pxzUsyHG8XJgYds8dFHudd/l2BqSol/P+6q+BIMHyZ9vo1hOsKUWwkL1g3 15 | He+PpB3xdAWOhsI= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /cert/serverTLS/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKQ5yoYypbUjGU2P 3 | usmaz576TitPGYBtiAFg8WyE+j/du2aUJTOv2FnZGS8blxwpeGOQy/hfx4MDq154 4 | Pni4wPJuYH5F0VPLViRlok6cyyNhyZguzFMhWnQHaAhWy41WqDGtoaQDn+zijDHl 5 | VKfQV8/xBOaWDT3atyvii5dp3Nz3AgMBAAECgYA1HnRBf/RVaKrwiYLzXt21IGVU 6 | YNtwzPzm5UJxxXlkWwYYoeZ+l2w1c6FPuW7hLxC/s8Al8GUKHY+6mM4sIiZWPzn9 7 | KUyD4SUblvVW2rq+US3gSuqPxEEZqTuA6gkxpsVqVq+x0a3yLKNNn7fYaOigViAs 8 | Q1aiWfkx3W3md1+ycQJBANl3Y+8/j8fOWEHvuTLm4y4o9MtKEeqRTvsl68C70qxk 9 | L20mrOdh9ZiJ0cQpMPiRn8IJZ+aELq6ZLSYRSBhroZUCQQDBU1L2QBC0UfN1yH4t 10 | arL6jRScBFwijxsFMQJo3pTE5M67WO1iVmkqG/Na82D/zsWP5zOTN4u/T/hT/mey 11 | j3lbAkEAiUqdterb/BnikGYm9uLbZEYJHdq6auuoAFjVeM1bWOi7JF75Y+7tXYsM 12 | +pnCx/sv8BF+HgMvcZ1U6qaYbIpu7QJAUecwLZ1ktlBF2a+QLUOYAegFRDfgpM+F 13 | /gQOZkluM6BQKTT6lP/Q3AMtbWnQvW9naZjk08jjjrCdn1AXtKFuIwJBAJoa5/dr 14 | TdNSG+1lc2aLRd5jwPFKY91JHgUoepAjm6m6AiyR0UPdhupPuse1Oy0Ompcewe3n 15 | SQBHyU0SjS10gEs= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /examples/clientTLS/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANFS7JETOMWausam 3 | ilqbbQq8k4Um8fYdGKkJ2xwbgcO+d1BeEaCSlZY5DokO4Fc55JrkUt8eBthaAwy2 4 | 3vPrZ3JIhoPkYbHyfr4kv+xDAfEz8GWWFZcCGZFeO28q3hgG3zDsMOYfHjXPeaQz 5 | Xil/kG2vXCvEqwFtHYUsj1AJ+5DfAgMBAAECgYB5K4h3nmnhsWTnM2apdolTir/W 6 | dynfbw5kY6krhxzB49KLeji3+8KNuC+a1Tq2I3Qa2YrDT01lFg3n4UAEcMNrDy64 7 | tAPwhngpNQfh55g0Y8g+sU3ORCKGlcnRj+Hkjoeoqch7N+D4/OxtsV2kpIwx91Cy 8 | VPsJXy5UjMueUx84oQJBAPCOd3CAy4gb4baxs426snPA+XrAv30opqpQPjfC7niq 9 | TC5ekHQLz40EHltoVn43ZuxIxo14/lG2b2/66ZyUdDECQQDewybP7kjVM+5BxINi 10 | pd9yya3V6vYg9rOmd5hYT1r5p27q/DaA4AowsE64Z3l4m1X5Q8SDchNXYZSQpGKo 11 | LmIPAkEAuTc4R2sjSt0M4TnZYdrJ485FSHp39coC6Ud3YD+6lTWqClnNJ0ygtZbi 12 | OMcRMYU+vdca6Xqrdgbdrn6muGSlQQJAYPJ5r8C6fd9N792AeKxL5P8U4qaRW6NC 13 | aE5gRP60TzapNSdy6P+YL7xrZiHSYcTo08TEf5hkBoF3UMPaE+urzQJBAJCe9ViG 14 | mmhfEZCi0h8NVfimcrlR9lzw/WGXUJQR4sVLsmCww8nTsYxZEBhErBL38iN07kJX 15 | bALt7CgDxJK61gk= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /examples/serverTLS/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMOECpAH//xAfom+ 3 | 9lUxMTudmq/S56ehs6W9b6B40uN4gIHyLg08ISfLzx+HAmIvGhBqub0dSg4yt98x 4 | tszBKG40uddWiY43NIs2iJFJ4J0vVdZsN7DPmaeKAi8Xe313btfSvsow2npz1tTY 5 | QJJ6ebW1ZMJY5ehEnQHx6afe5x1BAgMBAAECgYBT638lJ2nm+j/WHBNbqcu6DQHy 6 | ahJoz21uvIsHVBUg8f4myK6fm6mXjVGP9ME/J3yGg2jKFaJNydNGh+4woNAbDOBK 7 | 0VVS2orCwsPOamgxjDgtSa8O5W/PCibQr3n+SfPcjrgXe2ucHqaybNy+BCJMd6VP 8 | 2hYXMa+fX2gP7UucKQJBAPOvTaCzO44R6N5I0vW0oMM6WPrOzmim/hY0QH/Fh6aC 9 | yY92czOx37eYigg0fBgRNekJoz/q9nHXt+rCbFtQVBsCQQDNZYv4fR34Kx9M4uNr 10 | wUp7x/gaKwyyfqShIHLbr6WDSuXHLkrDGuKJZmE0RUktDr5bOCAIBGpx3CiQk9hc 11 | FBHTAkAkBNptkXCRHFaWXJ9KECvfD4XNdLoPv/c4ovqRqEnXs+dFHFYE4xrVUWe9 12 | tLxO9mRIkNMfhIRu6VrXCWvox2PXAkEAxbZDBx4A9DsmNQ+OFNKKDR7PbvkTjtoj 13 | RMqEZX3tV7WH+7RO2QIPIG3BkdxD9TBlAk/DXZRKlnFzRsBaG0BiawJBAJ8YlJ8i 14 | 6VWoxClYBFOKYEDrmQ3ZrcfDkJ7FdJ5uJIvf1R7tawq9ldZ9FWj7886Liv3c5zp1 15 | DZpr1REZVtMZsqM= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /examples/clientTLS/tlsclient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "github.com/amahi/spdy" 7 | "io" 8 | "net/http" 9 | ) 10 | 11 | func handle(err error) { 12 | if err != nil { 13 | panic(err) 14 | } 15 | } 16 | 17 | func main() { 18 | cert, err := tls.LoadX509KeyPair("client.pem", "client.key") 19 | if err != nil { 20 | fmt.Printf("server: loadkeys: %s", err) 21 | } 22 | config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true, NextProtos: []string{"spdy/3"}} 23 | conn, err := tls.Dial("tcp", "127.0.0.1:4040", &config) 24 | if err != nil { 25 | fmt.Printf("client: dial: %s", err) 26 | } 27 | client, err := spdy.NewClientConn(conn) 28 | handle(err) 29 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 30 | handle(err) 31 | res, err := client.Do(req) 32 | handle(err) 33 | data := make([]byte, int(res.ContentLength)) 34 | _, err = res.Body.(io.Reader).Read(data) 35 | fmt.Println(string(data)) 36 | res.Body.Close() 37 | } 38 | -------------------------------------------------------------------------------- /cert/clientTLS/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8jCCAlugAwIBAgIJAIesrI0XBN7DMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t 5 | LmNvbTElMCMGCSqGSIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTAeFw0x 6 | NDA2MTkxNTI1MDRaFw0yNDA2MTYxNTI1MDRaMIGRMQswCQYDVQQGEwJERTEMMAoG 7 | A1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBh 8 | bnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTElMCMGCSqG 9 | SIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEF 10 | AAOBjQAwgYkCgYEAuM/Pjl0weRg3eYjqa7OtG5mangY8qZyuAIKtUrBrdFqcNA3p 11 | EeaeEgKpKO3rbaUptGa0UZz4SQGN4vQaPAhUMvSwzkv2VE1c8UJujBymbNS3sJaN 12 | pxqnaG3Oc/LMTfiBwrVwp0tc/9anYw7lM83okMJvVf6h68Bs+D1hCj4oYQMCAwEA 13 | AaNQME4wHQYDVR0OBBYEFN7Mr6vFLqU0WYt63VS5BL0wlbAoMB8GA1UdIwQYMBaA 14 | FN7Mr6vFLqU0WYt63VS5BL0wlbAoMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF 15 | BQADgYEAYG8FxugPdzqwpJ+rL1jIagbwBrO+edlh0xslkcUBmIX4JxY17JZvdRC6 16 | J+CkkX0fCAdUDN+RjkbbO+cb4I4wx6UMaI07XeMAlSLo8KOCgH0EnnNC7JWvDW2z 17 | fPguHDquTs8mhgK2Kvoiro3VDfq6nqW/ya1UPGE8powqZ9032a8= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /cert/serverTLS/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8jCCAlugAwIBAgIJALr70ZZZtUIiMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t 5 | LmNvbTElMCMGCSqGSIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTAeFw0x 6 | NDA2MTkxNTI1MDRaFw0yNDA2MTYxNTI1MDRaMIGRMQswCQYDVQQGEwJERTEMMAoG 7 | A1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBh 8 | bnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTElMCMGCSqG 9 | SIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEF 10 | AAOBjQAwgYkCgYEApDnKhjKltSMZTY+6yZrPnvpOK08ZgG2IAWDxbIT6P927ZpQl 11 | M6/YWdkZLxuXHCl4Y5DL+F/HgwOrXng+eLjA8m5gfkXRU8tWJGWiTpzLI2HJmC7M 12 | UyFadAdoCFbLjVaoMa2hpAOf7OKMMeVUp9BXz/EE5pYNPdq3K+KLl2nc3PcCAwEA 13 | AaNQME4wHQYDVR0OBBYEFGRcvu82R6jFn95n8widL+IkARTYMB8GA1UdIwQYMBaA 14 | FGRcvu82R6jFn95n8widL+IkARTYMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF 15 | BQADgYEAVe4veg0dgJ4XsHZp0aGrD7cvZDenoOI9vLA29yLk69pe8qgrUunHxDmm 16 | U6UaJSn3w2/kDy9dNp9pypW7K0FK7l1J6HFVofJAETlpTSKrECZ9bLP/ETqUuTVJ 17 | Q4DPecYkNavM/acR35cosToyprokxT6OB8xboakZuVWXfGpwAmU= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /examples/clientTLS/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8jCCAlugAwIBAgIJAMW5uPSb8h51MA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t 5 | LmNvbTElMCMGCSqGSIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTAeFw0x 6 | NDA2MTkxNTA0MDdaFw0yNDA2MTYxNTA0MDdaMIGRMQswCQYDVQQGEwJERTEMMAoG 7 | A1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBh 8 | bnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTElMCMGCSqG 9 | SIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEF 10 | AAOBjQAwgYkCgYEA0VLskRM4xZq6xqaKWpttCryThSbx9h0YqQnbHBuBw753UF4R 11 | oJKVljkOiQ7gVznkmuRS3x4G2FoDDLbe8+tnckiGg+RhsfJ+viS/7EMB8TPwZZYV 12 | lwIZkV47byreGAbfMOww5h8eNc95pDNeKX+Qba9cK8SrAW0dhSyPUAn7kN8CAwEA 13 | AaNQME4wHQYDVR0OBBYEFOuNTdluCe67MUgP8mmDmWagFtnxMB8GA1UdIwQYMBaA 14 | FOuNTdluCe67MUgP8mmDmWagFtnxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF 15 | BQADgYEAKlPQMDjJjxfJwwYp5sw1kI8vJIKtl1EA8mKlgBRg80ZOtlhGl5GibDn0 16 | CNnLWJM/QfVsmM1i+HlOZhO+VFx3qA0lJF3i1NrPpzx6Vb22M9WTON4crSAWx6ul 17 | YXtrKL+eBerLo7enhyiBuQi5ie6CiDD1yar9h4hcBA+bKpglW6Y= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /examples/serverTLS/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8jCCAlugAwIBAgIJAN5cZfMnvtbkMA0GCSqGSIb3DQEBBQUAMIGRMQswCQYD 3 | VQQGEwJERTEMMAoGA1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwO 4 | UmFuZG9tIENvbXBhbnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9t 5 | LmNvbTElMCMGCSqGSIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTAeFw0x 6 | NDA2MTkxNTAzMTFaFw0yNDA2MTYxNTAzMTFaMIGRMQswCQYDVQQGEwJERTEMMAoG 7 | A1UECAwDTlJXMQ4wDAYDVQQHDAVFYXJ0aDEXMBUGA1UECgwOUmFuZG9tIENvbXBh 8 | bnkxCzAJBgNVBAsMAklUMRcwFQYDVQQDDA53d3cucmFuZG9tLmNvbTElMCMGCSqG 9 | SIb3DQEJARYWbmlsZXNoamFnbmlrQGdtYWlsLmNvbTCBnzANBgkqhkiG9w0BAQEF 10 | AAOBjQAwgYkCgYEAw4QKkAf//EB+ib72VTExO52ar9Lnp6Gzpb1voHjS43iAgfIu 11 | DTwhJ8vPH4cCYi8aEGq5vR1KDjK33zG2zMEobjS511aJjjc0izaIkUngnS9V1mw3 12 | sM+Zp4oCLxd7fXdu19K+yjDaenPW1NhAknp5tbVkwljl6ESdAfHpp97nHUECAwEA 13 | AaNQME4wHQYDVR0OBBYEFBwwnrLTRRbQjvFbwebDkaghdFmRMB8GA1UdIwQYMBaA 14 | FBwwnrLTRRbQjvFbwebDkaghdFmRMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF 15 | BQADgYEAKB32e80y2fiD4Zh54CGxkPSCOsU689KtjsD39nKeXLAYtSK92ZeKfnii 16 | vuTJDdputbzBvNjstDeXVnomvD2TIWPhriBU/ZO2lu0+nj4suUn2mdUFi2+asAPD 17 | W/ZsvPjHCH/SKUzFKOdDOEIUEHq+aQjLyShi/ME67lh+fCQXLv0= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /misc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Miscellaneous functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "fmt" 11 | "net" 12 | "net/url" 13 | "syscall" 14 | ) 15 | 16 | // PriorityFor returns the recommended priority for the given URL 17 | // for best opteration with the library. 18 | func PriorityFor(req *url.URL) uint8 { 19 | // FIXME: need to implement priorities properly 20 | return 4 21 | } 22 | 23 | // check to see if err is a connection reset 24 | func isConnReset(err error) bool { 25 | if e, ok := err.(*net.OpError); ok { 26 | if errno, ok := e.Err.(syscall.Errno); ok { 27 | return errno == syscall.ECONNRESET 28 | } 29 | } 30 | return false 31 | } 32 | 33 | // check to see if err is an network timeout 34 | func isBrokenPipe(err error) bool { 35 | if e, ok := err.(*net.OpError); ok { 36 | return e.Err == syscall.EPIPE 37 | } 38 | return false 39 | } 40 | 41 | // return best guess at a string for a network error 42 | func netErrorString(err error) string { 43 | if e, ok := err.(*net.OpError); ok { 44 | return fmt.Sprintf("%s", e.Err) 45 | } 46 | return fmt.Sprintf("%#v", err) 47 | } 48 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Amahi SPDY is a library built from scratch in the "Go way" for building SPDY clients and servers in the Go programming language. 2 | // 3 | // It supports a subset of SPDY 3.1 http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1 4 | // 5 | // Check the source code, examples and overview at https://github.com/amahi/spdy 6 | // 7 | // This library is used in a streaming server/proxy implementation for Amahi, the [home and media server](https://www.amahi.org). 8 | // 9 | // Goals 10 | // 11 | // The goals are reliability, streaming and performance/scalability. 12 | // 13 | // 1) Design for reliability means that network connections are assumed to disconnect at any time, especially when it's most inapropriate for the library to handle. This also includes potential issues with bugs in within the library, so the library tries to handle all crazy errors in the most reasonable way. A client or a server built with this library should be able to run for months and months of reliable operation. It's not there yet, but it will be. 14 | // 15 | // 2) Streaming requests, unlike typical HTTP requests (which are short), require working with an arbitrary large number of open requests (streams) simultaneously, and most of them are flow-constrained at the client endpoint. Streaming clients kind of misbehave too, for example, they open and close many streams rapidly with Range request to check certain parts of the file. This is common with endpoint clients like VLC or Quicktime (Safari on iOS or Mac OS X). We wrote this library with the goal of making it not just suitable for HTTP serving, but also for streaming. 16 | // 17 | // 3) The library was built with performance and scalability in mind, so things have been done using as little blocking and copying of data as possible. It was meant to be implemented in the "go way", using concurrency extensively and channel communication. The library uses mutexes very sparingly so that handling of errors at all manner of inapropriate times becomes easier. It goes to great lengths to not block, establishing timeouts when network and even channel communication may fail. The library should use very very little CPU, even in the presence of many streams and sessions running simultaneously. 18 | package spdy 19 | -------------------------------------------------------------------------------- /client_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // client connection related functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "errors" 12 | "net" 13 | "net/http" 14 | "time" 15 | ) 16 | 17 | func handle(err error) { 18 | if err != nil { 19 | panic(err) 20 | } 21 | } 22 | 23 | // NewRecorder returns an initialized ResponseRecorder. 24 | func NewRecorder() *ResponseRecorder { 25 | return &ResponseRecorder{ 26 | HeaderMap: make(http.Header), 27 | Body: new(bytes.Buffer), 28 | Code: 200, 29 | } 30 | } 31 | 32 | // Header returns the response headers. 33 | func (rw *ResponseRecorder) Header() http.Header { 34 | m := rw.HeaderMap 35 | if m == nil { 36 | m = make(http.Header) 37 | rw.HeaderMap = m 38 | } 39 | return m 40 | } 41 | 42 | // Write always succeeds and writes to rw.Body. 43 | func (rw *ResponseRecorder) Write(buf []byte) (int, error) { 44 | if !rw.wroteHeader { 45 | rw.WriteHeader(200) 46 | } 47 | if rw.Body != nil { 48 | len, err := rw.Body.Write(buf) 49 | return len, err 50 | } else { 51 | rw.Body = new(bytes.Buffer) 52 | len, err := rw.Body.Write(buf) 53 | return len, err 54 | } 55 | return len(buf), nil 56 | } 57 | 58 | // WriteHeader sets rw.Code. 59 | func (rw *ResponseRecorder) WriteHeader(code int) { 60 | if !rw.wroteHeader { 61 | rw.Code = code 62 | } 63 | rw.wroteHeader = true 64 | } 65 | 66 | //returns a client that reads and writes on c 67 | func NewClientConn(c net.Conn) (*Client, error) { 68 | session := NewClientSession(c) 69 | go session.Serve() 70 | return &Client{cn: c, ss: session}, nil 71 | } 72 | 73 | //returns a client with tcp connection created using net.Dial 74 | func NewClient(addr string) (*Client, error) { 75 | conn, err := net.Dial("tcp", addr) 76 | if err != nil { 77 | return &Client{}, err 78 | } 79 | session := NewClientSession(conn) 80 | go session.Serve() 81 | return &Client{cn: conn, ss: session}, nil 82 | } 83 | 84 | //to get a response from the client 85 | func (c *Client) Do(req *http.Request) (*http.Response, error) { 86 | rr := NewRecorder() 87 | err := c.ss.NewStreamProxy(req, rr) 88 | if err != nil { 89 | return &http.Response{}, err 90 | } 91 | resp := &http.Response{ 92 | StatusCode: rr.Code, 93 | Proto: "HTTP/1.1", 94 | ProtoMajor: 1, 95 | ProtoMinor: 1, 96 | Body: &readCloser{rr.Body}, 97 | ContentLength: int64(rr.Body.Len()), 98 | Header: rr.Header(), 99 | } 100 | return resp, nil 101 | } 102 | 103 | func (c *Client) Close() error { 104 | if c.cn == nil { 105 | err := errors.New("No connection to close") 106 | return err 107 | } 108 | err := c.cn.Close() 109 | return err 110 | } 111 | func (c *Client) Ping(d time.Duration) (pinged bool, err error) { 112 | if c.cn == nil { 113 | err := errors.New("No connection estabilished to server") 114 | return false, err 115 | } 116 | ping := c.ss.Ping(d) 117 | return ping, nil 118 | } 119 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // friendly API tests 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "crypto/tls" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "io/ioutil" 16 | "net/http" 17 | "testing" 18 | "time" 19 | ) 20 | 21 | const SERVER_CERTFILE = "cert/serverTLS/server.pem" 22 | const SERVER_KEYFILE = "cert/serverTLS/server.key" 23 | const CLIENT_CERTFILE = "cert/clientTLS/client.pem" 24 | const CLIENT_KEYFILE = "cert/clientTLS/client.key" 25 | 26 | func init() { 27 | SetLog(ioutil.Discard) 28 | } 29 | 30 | //handler for requests 31 | func ServerHandler(w http.ResponseWriter, r *http.Request) { 32 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 33 | } 34 | 35 | func StartServer(server *Server, done chan<- error) { 36 | err := server.ListenAndServe() 37 | if err != nil { 38 | done <- err 39 | } 40 | done <- errors.New("") 41 | } 42 | 43 | func testclient(done chan<- error) { 44 | //make client 45 | client, err := NewClient("localhost:4040") 46 | if err != nil { 47 | done <- err 48 | } 49 | for i := 0; i < 100; i++ { 50 | //now send requests and test 51 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 52 | if err != nil { 53 | done <- err 54 | } 55 | res, err := client.Do(req) 56 | if err != nil { 57 | done <- err 58 | } 59 | data := make([]byte, int(res.ContentLength)) 60 | _, err = res.Body.(io.Reader).Read(data) 61 | if string(data) != "Hi there, I love banana!" { 62 | done <- err 63 | } 64 | res.Body.Close() 65 | 66 | //another request 67 | req, err = http.NewRequest("POST", "http://localhost:4040/monkeys", bytes.NewBufferString("hello=world")) 68 | if err != nil { 69 | done <- err 70 | } 71 | 72 | res, err = client.Do(req) 73 | if err != nil { 74 | done <- err 75 | } 76 | data = make([]byte, int(res.ContentLength)) 77 | _, err = res.Body.(io.Reader).Read(data) 78 | if string(data) != "Hi there, I love monkeys!" { 79 | done <- err 80 | } 81 | res.Body.Close() 82 | } 83 | 84 | //close client 85 | err = client.Close() 86 | if err != nil { 87 | done <- err 88 | } 89 | done <- errors.New("") 90 | } 91 | 92 | //simple server with many clients sending requests to it 93 | func TestSimpleServerClient(t *testing.T) { 94 | //make server 95 | mux := http.NewServeMux() 96 | mux.HandleFunc("/", ServerHandler) 97 | server := &Server{ 98 | Addr: "localhost:4040", 99 | Handler: mux, 100 | } 101 | serverdone := make(chan error) 102 | go StartServer(server, serverdone) 103 | time.Sleep(100 * time.Millisecond) 104 | 105 | cldone1 := make(chan error) 106 | cldone2 := make(chan error) 107 | cldone3 := make(chan error) 108 | cldone4 := make(chan error) 109 | cldone5 := make(chan error) 110 | 111 | go testclient(cldone1) 112 | go testclient(cldone2) 113 | go testclient(cldone3) 114 | go testclient(cldone4) 115 | go testclient(cldone5) 116 | 117 | err := <-cldone1 118 | if err.Error() != "" { 119 | t.Fatal(err.Error()) 120 | } 121 | err = <-cldone2 122 | if err.Error() != "" { 123 | t.Fatal(err.Error()) 124 | } 125 | err = <-cldone3 126 | if err.Error() != "" { 127 | t.Fatal(err.Error()) 128 | } 129 | err = <-cldone4 130 | if err.Error() != "" { 131 | t.Fatal(err.Error()) 132 | } 133 | err = <-cldone5 134 | if err.Error() != "" { 135 | t.Fatal(err.Error()) 136 | } 137 | 138 | server.Close() 139 | time.Sleep(100 * time.Millisecond) 140 | err = <-serverdone 141 | } 142 | 143 | func TestTLSServerSpdyOnly(t *testing.T) { 144 | //make server 145 | mux := http.NewServeMux() 146 | mux.HandleFunc("/", ServerHandler) 147 | server := &Server{ 148 | Addr: "localhost:4040", 149 | Handler: mux, 150 | } 151 | go server.ListenAndServeTLSSpdyOnly(SERVER_CERTFILE, SERVER_KEYFILE) 152 | time.Sleep(400 * time.Millisecond) 153 | 154 | //client 155 | cert, err := tls.LoadX509KeyPair(CLIENT_CERTFILE, CLIENT_KEYFILE) 156 | if err != nil { 157 | t.Fatal(err.Error()) 158 | } 159 | config := tls.Config{Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true} 160 | conn, err := tls.Dial("tcp", "127.0.0.1:4040", &config) 161 | if err != nil { 162 | t.Fatal(err.Error()) 163 | } 164 | client, err := NewClientConn(conn) 165 | if err != nil { 166 | t.Fatal(err.Error()) 167 | } 168 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 169 | if err != nil { 170 | t.Fatal(err.Error()) 171 | } 172 | res, err := client.Do(req) 173 | if err != nil { 174 | t.Fatal(err.Error()) 175 | } 176 | data := make([]byte, int(res.ContentLength)) 177 | _, err = res.Body.(io.Reader).Read(data) 178 | if string(data) != "Hi there, I love banana!" { 179 | t.Fatal("Unexpected Data") 180 | } 181 | res.Body.Close() 182 | 183 | //close client 184 | err = client.Close() 185 | if err != nil { 186 | t.Fatal(err.Error()) 187 | } 188 | //server close 189 | server.Close() 190 | time.Sleep(100 * time.Millisecond) 191 | } 192 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Data structures and types for the Amahi SPDY library 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "crypto/tls" 12 | "io" 13 | "net" 14 | "net/http" 15 | ) 16 | 17 | type streamID uint32 18 | 19 | // Kinds of control frames 20 | type controlFrameKind uint16 21 | 22 | const ( 23 | FRAME_SYN_STREAM = 0x0001 24 | FRAME_SYN_REPLY = 0x0002 25 | FRAME_RST_STREAM = 0x0003 26 | FRAME_SETTINGS = 0x0004 27 | FRAME_PING = 0x0006 28 | FRAME_GOAWAY = 0x0007 29 | FRAME_HEADERS = 0x0008 30 | FRAME_WINDOW_UPDATE = 0x0009 31 | ) 32 | 33 | // Frame flags 34 | type frameFlags uint8 35 | 36 | const ( 37 | FLAG_NONE = frameFlags(0x00) 38 | FLAG_FIN = frameFlags(0x01) 39 | ) 40 | 41 | type dataFrame struct { 42 | stream streamID 43 | flags frameFlags 44 | data []byte 45 | } 46 | 47 | type controlFrame struct { 48 | kind controlFrameKind 49 | flags frameFlags 50 | data []byte 51 | } 52 | 53 | type frame interface { 54 | Write(io.Writer) (n int64, err error) 55 | Flags() frameFlags 56 | String() string 57 | Data() []byte 58 | } 59 | 60 | type Session struct { 61 | conn net.Conn // the underlying connection 62 | out chan frame // channel to send a frame 63 | in chan frame // channel to receive a frame 64 | new_stream chan *Stream // channel to register new streams 65 | end_stream chan *Stream // channel to unregister streams 66 | streams map[streamID]*Stream 67 | server *http.Server // http server for this session 68 | nextStream streamID // the next stream ID 69 | closed bool // is this session closed? 70 | goaway_recvd bool // recieved goaway 71 | headerWriter *headerWriter 72 | headerReader *headerReader 73 | settings *settings 74 | nextPing uint32 // the next ping ID 75 | // channel to send our self-initiated pings 76 | // Ping() listens for an outstanding ping 77 | pinger chan uint32 78 | } 79 | 80 | type settings struct { 81 | flags frameFlags 82 | count uint32 83 | svp []settingsValuePairs 84 | } 85 | 86 | type settingsValuePairs struct { 87 | flags uint8 88 | id uint32 89 | value uint32 90 | } 91 | 92 | type Stream struct { 93 | id streamID 94 | session *Session 95 | priority uint8 96 | associated_stream streamID 97 | headers http.Header 98 | response_writer http.ResponseWriter 99 | closed bool 100 | wroteHeader bool 101 | // IMPORTANT, these channels must not block (for long) 102 | control chan controlFrame // control frames arrive here 103 | data chan dataFrame // data frames arrive here 104 | response chan bool // http responses sent here 105 | eos chan bool // frame handlers poke this when stream is ending 106 | stop_server chan bool // when stream is closed, to stop the server 107 | flow_req chan int32 // control flow requests 108 | flow_add chan int32 // control flow additions 109 | upstream_buffer chan upstream_data 110 | } 111 | 112 | type upstream_data struct { 113 | data []byte 114 | final bool 115 | } 116 | 117 | type frameSynStream struct { 118 | session *Session 119 | stream streamID 120 | priority uint8 121 | associated_stream streamID 122 | header http.Header 123 | flags frameFlags 124 | } 125 | 126 | type frameSynReply struct { 127 | session *Session 128 | stream streamID 129 | headers http.Header 130 | flags frameFlags 131 | } 132 | 133 | // maximum number of bytes in a frame 134 | const MAX_DATA_PAYLOAD = 1<<24 - 1 135 | 136 | const ( 137 | HEADER_STATUS string = ":status" 138 | HEADER_VERSION string = ":version" 139 | HEADER_PATH string = ":path" 140 | HEADER_METHOD string = ":method" 141 | HEADER_HOST string = ":host" 142 | HEADER_SCHEME string = ":scheme" 143 | HEADER_CONTENT_LENGTH string = "Content-Length" 144 | ) 145 | 146 | type readCloser struct { 147 | io.Reader 148 | } 149 | 150 | // ResponseRecorder is an implementation of http.ResponseWriter that 151 | // is used to get a response. 152 | type ResponseRecorder struct { 153 | Code int // the HTTP response code from WriteHeader 154 | HeaderMap http.Header // the HTTP response headers 155 | Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to 156 | wroteHeader bool 157 | } 158 | 159 | //spdy client 160 | type Client struct { 161 | cn net.Conn 162 | ss *Session 163 | } 164 | 165 | //spdy server 166 | type Server struct { 167 | Handler http.Handler 168 | Addr string 169 | TLSConfig *tls.Config 170 | ln net.Listener 171 | //channel on which the server passes any new spdy 'Session' structs that get created during its lifetime 172 | ss_chan chan *Session 173 | } 174 | 175 | //spdy conn 176 | type conn struct { 177 | srv *Server 178 | ss *Session 179 | cn net.Conn 180 | } 181 | -------------------------------------------------------------------------------- /spdy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // lower level test functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "net/http" 16 | "testing" 17 | "time" 18 | ) 19 | 20 | func init() { 21 | SetLog(ioutil.Discard) 22 | } 23 | 24 | func ServerTestHandler(w http.ResponseWriter, r *http.Request) { 25 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 26 | } 27 | 28 | func TestFrames(t *testing.T) { 29 | //make server 30 | mux := http.NewServeMux() 31 | mux.HandleFunc("/", ServerTestHandler) 32 | server := &Server{ 33 | Addr: "localhost:4040", 34 | Handler: mux, 35 | } 36 | go server.ListenAndServe() 37 | time.Sleep(200 * time.Millisecond) 38 | 39 | //make client 40 | client, err := NewClient("localhost:4040") 41 | if err != nil { 42 | t.Fatal(err.Error()) 43 | } 44 | 45 | //now send requests and test 46 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 47 | if err != nil { 48 | t.Fatal(err.Error()) 49 | } 50 | res, err := client.Do(req) 51 | if err != nil { 52 | t.Fatal(err.Error()) 53 | } 54 | data := make([]byte, int(res.ContentLength)) 55 | _, err = res.Body.(io.Reader).Read(data) 56 | if string(data) != "Hi there, I love banana!" { 57 | t.Fatal("Unexpected Data") 58 | } 59 | res.Body.Close() 60 | 61 | //another request 62 | req, err = http.NewRequest("POST", "http://localhost:4040/monkeys", bytes.NewBufferString("hello=world")) 63 | if err != nil { 64 | t.Fatal(err.Error()) 65 | } 66 | 67 | res, err = client.Do(req) 68 | if err != nil { 69 | t.Fatal(err.Error()) 70 | } 71 | data = make([]byte, int(res.ContentLength)) 72 | _, err = res.Body.(io.Reader).Read(data) 73 | if string(data) != "Hi there, I love monkeys!" { 74 | t.Fatal("Unexpected Data") 75 | } 76 | res.Body.Close() 77 | 78 | //settings frame test 79 | set := new(settings) 80 | var svp []settingsValuePairs 81 | svp = append(svp, settingsValuePairs{flags: 0, id: 4, value: 6}) //set SETTINGS_MAX_CONCURRENT_STREAMS to 6 82 | svp = append(svp, settingsValuePairs{flags: 0, id: 3, value: 400}) //set SETTINGS_ROUND_TRIP_TIME to 400ms 83 | set.flags = 0 84 | set.count = 2 85 | set.svp = svp 86 | buf := new(bytes.Buffer) 87 | err = binary.Write(buf, binary.BigEndian, &set.count) 88 | if err != nil { 89 | t.Fatal(err.Error()) 90 | } 91 | 92 | for i := uint32(0); i < set.count; i++ { 93 | err = binary.Write(buf, binary.BigEndian, &set.svp[i].flags) 94 | if err != nil { 95 | t.Fatal(err.Error()) 96 | } 97 | err = binary.Write(buf, binary.BigEndian, &set.svp[i].id) 98 | if err != nil { 99 | t.Fatal(err.Error()) 100 | } 101 | err = binary.Write(buf, binary.BigEndian, &set.svp[i].value) 102 | if err != nil { 103 | t.Fatal(err.Error()) 104 | } 105 | } 106 | settings_frame := controlFrame{kind: FRAME_SETTINGS, flags: 0, data: buf.Bytes()} 107 | client.ss.out <- settings_frame 108 | time.Sleep(200 * time.Millisecond) 109 | 110 | //rstStreamtest - first, start stream on client 111 | //FIXME - need to add a proper test 112 | str := client.ss.NewClientStream() 113 | if str == nil { 114 | t.Fatal("ERROR in NewClientStream: cannot create stream") 115 | return 116 | } 117 | str.sendRstStream() 118 | 119 | //ping test 120 | ping, err := client.Ping(time.Second) 121 | if err != nil { 122 | t.Fatal(err.Error()) 123 | } 124 | 125 | if ping == false { 126 | t.Fatal("Unable to ping server from client") 127 | } 128 | 129 | //close client 130 | err = client.Close() 131 | if err != nil { 132 | t.Fatal(err.Error()) 133 | } 134 | //server close 135 | server.Close() 136 | } 137 | 138 | func TestGoaway(t *testing.T) { 139 | //make server 140 | mux := http.NewServeMux() 141 | mux.HandleFunc("/", ServerTestHandler) 142 | server_session_chan := make(chan *Session) 143 | server := &Server{ 144 | Addr: "localhost:4040", 145 | Handler: mux, 146 | ss_chan: server_session_chan, 147 | } 148 | go server.ListenAndServe() 149 | time.Sleep(200 * time.Millisecond) 150 | 151 | //make client 152 | client, err := NewClient("localhost:4040") 153 | if err != nil { 154 | t.Fatal(err.Error()) 155 | } 156 | 157 | //prepare a request 158 | request, err := http.NewRequest("POST", "http://localhost:4040/banana", bytes.NewBufferString("hello world")) 159 | if err != nil { 160 | t.Fatal(err.Error()) 161 | } 162 | 163 | ss := <-server_session_chan 164 | 165 | client_stream1 := client.ss.NewClientStream() 166 | err = client_stream1.prepareRequestHeader(request) 167 | if err != nil { 168 | t.Fatal(err.Error()) 169 | } 170 | f := frameSynStream{session: client_stream1.session, stream: client_stream1.id, header: request.Header, flags: 2} 171 | client_stream1.session.out <- f 172 | 173 | client_stream2 := client.ss.NewClientStream() 174 | err = client_stream2.prepareRequestHeader(request) 175 | if err != nil { 176 | t.Fatal(err.Error()) 177 | } 178 | f = frameSynStream{session: client_stream2.session, stream: client_stream2.id, header: request.Header, flags: 2} 179 | client_stream2.session.out <- f 180 | time.Sleep(200 * time.Millisecond) 181 | 182 | dat := []byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00} 183 | ss.out <- controlFrame{kind: FRAME_GOAWAY, flags: 0, data: dat} 184 | time.Sleep(200 * time.Millisecond) 185 | 186 | if client.ss.NewClientStream() != nil { 187 | t.Fatal("Stream Made even after goaway sent") 188 | } 189 | 190 | if client_stream1.closed == true { 191 | t.Fatal("Stream#1 closed: unexpected") 192 | } 193 | 194 | if client_stream2.closed == false { 195 | t.Fatal("Stream#2 alive: unexpected") 196 | } 197 | 198 | //close client 199 | err = client.Close() 200 | if err != nil { 201 | t.Fatal(err.Error()) 202 | } 203 | //server close 204 | server.Close() 205 | } 206 | -------------------------------------------------------------------------------- /server_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // server connection related functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "crypto/tls" 11 | "net" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | func (c *conn) handleConnection(outchan chan *Session) { 17 | hserve := new(http.Server) 18 | if c.srv.Handler == nil { 19 | hserve.Handler = http.DefaultServeMux 20 | } else { 21 | hserve.Handler = c.srv.Handler 22 | } 23 | hserve.Addr = c.srv.Addr 24 | c.ss = NewServerSession(c.cn, hserve) 25 | if outchan != nil { 26 | outchan <- c.ss 27 | } 28 | c.ss.Serve() 29 | } 30 | 31 | // ListenAndServe listens on the TCP network address s.Addr and then 32 | // calls Serve to handle requests on incoming connections. 33 | func (s *Server) ListenAndServe() (err error) { 34 | if s.Addr == "" { 35 | s.Addr = ":http" 36 | } 37 | ln, err := net.Listen("tcp", s.Addr) 38 | if err != nil { 39 | return err 40 | } 41 | return s.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}) 42 | } 43 | 44 | // Serve accepts incoming connections on the Listener l, creating a 45 | // new service goroutine for each. The service goroutines read requests and 46 | // then call srv.Handler to reply to them. 47 | func (s *Server) Serve(ln net.Listener) (err error) { 48 | s.ln = ln 49 | defer s.ln.Close() 50 | var tempDelay time.Duration // how long to sleep on accept failure 51 | for { 52 | rw, err := s.ln.Accept() 53 | if err != nil { 54 | if ne, ok := err.(net.Error); ok && ne.Temporary() { 55 | if tempDelay == 0 { 56 | tempDelay = 5 * time.Millisecond 57 | } else { 58 | tempDelay *= 2 59 | } 60 | if max := 1 * time.Second; tempDelay > max { 61 | tempDelay = max 62 | } 63 | log.Printf("http: Accept error: %v; retrying in %v", err, tempDelay) 64 | time.Sleep(tempDelay) 65 | continue 66 | } 67 | return err 68 | } 69 | tempDelay = 0 70 | c, err := s.newConn(rw) 71 | if err != nil { 72 | continue 73 | } 74 | go c.handleConnection(s.ss_chan) 75 | } 76 | } 77 | 78 | //close spdy server and return 79 | // Any blocked Accept operations will be unblocked and return errors. 80 | func (s *Server) Close() (err error) { 81 | return s.ln.Close() 82 | } 83 | 84 | // Create new connection from rw 85 | func (server *Server) newConn(rwc net.Conn) (c *conn, err error) { 86 | c = &conn{ 87 | srv: server, 88 | cn: rwc, 89 | } 90 | return c, nil 91 | } 92 | 93 | // ListenAndServe listens on the TCP network address addr 94 | // and then calls Serve with handler to handle requests 95 | // on incoming connections. Handler is typically nil, 96 | // in which case the DefaultServeMux is used. This creates a spdy 97 | // only server without TLS 98 | // 99 | // A trivial example server is: 100 | // 101 | // package main 102 | // 103 | // import ( 104 | // "io" 105 | // "net/http" 106 | // "github.com/amahi/spdy" 107 | // "log" 108 | // ) 109 | // 110 | // // hello world, the web server 111 | // func HelloServer(w http.ResponseWriter, req *http.Request) { 112 | // io.WriteString(w, "hello, world!\n") 113 | // } 114 | // 115 | // func main() { 116 | // http.HandleFunc("/hello", HelloServer) 117 | // err := spdy.ListenAndServe(":12345", nil) 118 | // if err != nil { 119 | // log.Fatal("ListenAndServe: ", err) 120 | // } 121 | // } 122 | func ListenAndServe(addr string, handler http.Handler) (err error) { 123 | server := &Server{ 124 | Addr: addr, 125 | Handler: handler, 126 | } 127 | return server.ListenAndServe() 128 | } 129 | 130 | // ListenAndServeTLS acts identically to ListenAndServe, except that it 131 | // expects HTTPS connections. Servers created this way have NPN Negotiation and 132 | // accept requests from both spdy and http clients. 133 | // Additionally, files containing a certificate and matching private 134 | // key for the server must be provided. If the certificate is signed by a certificate 135 | // authority, the certFile should be the concatenation of the server's certificate 136 | // followed by the CA's certificate. 137 | // 138 | // A trivial example server is: 139 | // 140 | // import ( 141 | // "log" 142 | // "net/http" 143 | // "github.com/amahi/spdy" 144 | // ) 145 | // 146 | // func handler(w http.ResponseWriter, req *http.Request) { 147 | // w.Header().Set("Content-Type", "text/plain") 148 | // w.Write([]byte("This is an example server.\n")) 149 | // } 150 | // 151 | // func main() { 152 | // http.HandleFunc("/", handler) 153 | // log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/") 154 | // err := spdy.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil) 155 | // if err != nil { 156 | // log.Fatal(err) 157 | // } 158 | // } 159 | // 160 | // One can use makecert.sh in /certs to generate certfile and keyfile 161 | func ListenAndServeTLS(addr string, certFile string, keyFile string, handler http.Handler) error { 162 | server := &http.Server{ 163 | Addr: addr, 164 | Handler: handler, 165 | TLSConfig: &tls.Config{ 166 | NextProtos: []string{"spdy/3.1", "spdy/3"}, 167 | }, 168 | TLSNextProto: map[string]func(*http.Server, *tls.Conn, http.Handler){ 169 | "spdy/3.1": nextproto3, 170 | "spdy/3": nextproto3, 171 | }, 172 | } 173 | if server.Handler == nil { 174 | server.Handler = http.DefaultServeMux 175 | } 176 | return server.ListenAndServeTLS(certFile, keyFile) 177 | } 178 | 179 | func nextproto3(s *http.Server, c *tls.Conn, h http.Handler) { 180 | server_session := NewServerSession(c, s) 181 | server_session.Serve() 182 | } 183 | 184 | func ListenAndServeTLSSpdyOnly(addr string, certFile string, keyFile string, handler http.Handler) error { 185 | server := &Server{ 186 | Addr: addr, 187 | Handler: handler, 188 | } 189 | return server.ListenAndServeTLSSpdyOnly(certFile, keyFile) 190 | } 191 | 192 | // ListenAndServeTLSSpdyOnly listens on the TCP network address srv.Addr and 193 | // then calls Serve to handle requests on incoming TLS connections. 194 | // This is a spdy-only server with TLS and no NPN. 195 | // 196 | // Filenames containing a certificate and matching private key for 197 | // the server must be provided. If the certificate is signed by a 198 | // certificate authority, the certFile should be the concatenation 199 | // of the server's certificate followed by the CA's certificate. 200 | func (srv *Server) ListenAndServeTLSSpdyOnly(certFile, keyFile string) error { 201 | addr := srv.Addr 202 | if addr == "" { 203 | addr = ":https" 204 | } 205 | config := &tls.Config{} 206 | if srv.TLSConfig != nil { 207 | *config = *srv.TLSConfig 208 | } 209 | if config.NextProtos == nil { 210 | config.NextProtos = []string{"spdy/3.1", "spdy/3"} 211 | } 212 | var err error 213 | config.Certificates = make([]tls.Certificate, 1) 214 | config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 215 | if err != nil { 216 | return err 217 | } 218 | 219 | ln, err := net.Listen("tcp", addr) 220 | if err != nil { 221 | return err 222 | } 223 | 224 | tlsListener := tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, config) 225 | srv.TLSConfig = config 226 | return srv.Serve(tlsListener) 227 | } 228 | 229 | // tcpKeepAliveListener sets TCP keep-alive timeouts on accepted 230 | // connections. It's used by ListenAndServe and ListenAndServeTLS so 231 | // dead TCP connections (e.g. closing laptop mid-download) eventually 232 | // go away. 233 | type tcpKeepAliveListener struct { 234 | *net.TCPListener 235 | } 236 | 237 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 238 | tc, err := ln.AcceptTCP() 239 | if err != nil { 240 | return 241 | } 242 | tc.SetKeepAlive(true) 243 | tc.SetKeepAlivePeriod(3 * time.Minute) 244 | return tc, nil 245 | } 246 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This library is provided with the GNU LGPL licence (listed below), 2 | with the explicit exception of permitting static linking inr Go 3 | works incorporating this library. This eliminates the requirement that 4 | you must provide your application in an object format to allow 5 | for modifying the library and relinking the application. 6 | 7 | GNU LESSER GENERAL PUBLIC LICENSE 8 | Version 3, 29 June 2007 9 | 10 | Copyright (C) 2007 Free Software Foundation, Inc. 11 | Everyone is permitted to copy and distribute verbatim copies 12 | of this license document, but changing it is not allowed. 13 | 14 | 15 | This version of the GNU Lesser General Public License incorporates 16 | the terms and conditions of version 3 of the GNU General Public 17 | License, supplemented by the additional permissions listed below. 18 | 19 | 0. Additional Definitions. 20 | 21 | As used herein, "this License" refers to version 3 of the GNU Lesser 22 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 23 | General Public License. 24 | 25 | "The Library" refers to a covered work governed by this License, 26 | other than an Application or a Combined Work as defined below. 27 | 28 | An "Application" is any work that makes use of an interface provided 29 | by the Library, but which is not otherwise based on the Library. 30 | Defining a subclass of a class defined by the Library is deemed a mode 31 | of using an interface provided by the Library. 32 | 33 | A "Combined Work" is a work produced by combining or linking an 34 | Application with the Library. The particular version of the Library 35 | with which the Combined Work was made is also called the "Linked 36 | Version". 37 | 38 | The "Minimal Corresponding Source" for a Combined Work means the 39 | Corresponding Source for the Combined Work, excluding any source code 40 | for portions of the Combined Work that, considered in isolation, are 41 | based on the Application, and not on the Linked Version. 42 | 43 | The "Corresponding Application Code" for a Combined Work means the 44 | object code and/or source code for the Application, including any data 45 | and utility programs needed for reproducing the Combined Work from the 46 | Application, but excluding the System Libraries of the Combined Work. 47 | 48 | 1. Exception to Section 3 of the GNU GPL. 49 | 50 | You may convey a covered work under sections 3 and 4 of this License 51 | without being bound by section 3 of the GNU GPL. 52 | 53 | 2. Conveying Modified Versions. 54 | 55 | If you modify a copy of the Library, and, in your modifications, a 56 | facility refers to a function or data to be supplied by an Application 57 | that uses the facility (other than as an argument passed when the 58 | facility is invoked), then you may convey a copy of the modified 59 | version: 60 | 61 | a) under this License, provided that you make a good faith effort to 62 | ensure that, in the event an Application does not supply the 63 | function or data, the facility still operates, and performs 64 | whatever part of its purpose remains meaningful, or 65 | 66 | b) under the GNU GPL, with none of the additional permissions of 67 | this License applicable to that copy. 68 | 69 | 3. Object Code Incorporating Material from Library Header Files. 70 | 71 | The object code form of an Application may incorporate material from 72 | a header file that is part of the Library. You may convey such object 73 | code under terms of your choice, provided that, if the incorporated 74 | material is not limited to numerical parameters, data structure 75 | layouts and accessors, or small macros, inline functions and templates 76 | (ten or fewer lines in length), you do both of the following: 77 | 78 | a) Give prominent notice with each copy of the object code that the 79 | Library is used in it and that the Library and its use are 80 | covered by this License. 81 | 82 | b) Accompany the object code with a copy of the GNU GPL and this license 83 | document. 84 | 85 | 4. Combined Works. 86 | 87 | You may convey a Combined Work under terms of your choice that, 88 | taken together, effectively do not restrict modification of the 89 | portions of the Library contained in the Combined Work and reverse 90 | engineering for debugging such modifications, if you also do each of 91 | the following: 92 | 93 | a) Give prominent notice with each copy of the Combined Work that 94 | the Library is used in it and that the Library and its use are 95 | covered by this License. 96 | 97 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 98 | document. 99 | 100 | c) For a Combined Work that displays copyright notices during 101 | execution, include the copyright notice for the Library among 102 | these notices, as well as a reference directing the user to the 103 | copies of the GNU GPL and this license document. 104 | 105 | d) Do one of the following: 106 | 107 | 0) Convey the Minimal Corresponding Source under the terms of this 108 | License, and the Corresponding Application Code in a form 109 | suitable for, and under terms that permit, the user to 110 | recombine or relink the Application with a modified version of 111 | the Linked Version to produce a modified Combined Work, in the 112 | manner specified by section 6 of the GNU GPL for conveying 113 | Corresponding Source. 114 | 115 | 1) Use a suitable shared library mechanism for linking with the 116 | Library. A suitable mechanism is one that (a) uses at run time 117 | a copy of the Library already present on the user's computer 118 | system, and (b) will operate properly with a modified version 119 | of the Library that is interface-compatible with the Linked 120 | Version. 121 | 122 | e) Provide Installation Information, but only if you would otherwise 123 | be required to provide such information under section 6 of the 124 | GNU GPL, and only to the extent that such information is 125 | necessary to install and execute a modified version of the 126 | Combined Work produced by recombining or relinking the 127 | Application with a modified version of the Linked Version. (If 128 | you use option 4d0, the Installation Information must accompany 129 | the Minimal Corresponding Source and Corresponding Application 130 | Code. If you use option 4d1, you must provide the Installation 131 | Information in the manner specified by section 6 of the GNU GPL 132 | for conveying Corresponding Source.) 133 | 134 | 5. Combined Libraries. 135 | 136 | You may place library facilities that are a work based on the 137 | Library side by side in a single library together with other library 138 | facilities that are not Applications and are not covered by this 139 | License, and convey such a combined library under terms of your 140 | choice, if you do both of the following: 141 | 142 | a) Accompany the combined library with a copy of the same work based 143 | on the Library, uncombined with any other library facilities, 144 | conveyed under the terms of this License. 145 | 146 | b) Give prominent notice with the combined library that part of it 147 | is a work based on the Library, and explaining where to find the 148 | accompanying uncombined form of the same work. 149 | 150 | 6. Revised Versions of the GNU Lesser General Public License. 151 | 152 | The Free Software Foundation may publish revised and/or new versions 153 | of the GNU Lesser General Public License from time to time. Such new 154 | versions will be similar in spirit to the present version, but may 155 | differ in detail to address new problems or concerns. 156 | 157 | Each version is given a distinguishing version number. If the 158 | Library as you received it specifies that a certain numbered version 159 | of the GNU Lesser General Public License "or any later version" 160 | applies to it, you have the option of following the terms and 161 | conditions either of that published version or of any later version 162 | published by the Free Software Foundation. If the Library as you 163 | received it does not specify a version number of the GNU Lesser 164 | General Public License, you may choose any version of the GNU Lesser 165 | General Public License ever published by the Free Software Foundation. 166 | 167 | If the Library as you received it specifies that a proxy can decide 168 | whether future versions of the GNU Lesser General Public License shall 169 | apply, that proxy's public statement of acceptance of any version is 170 | permanent authorization for you to choose that version for the 171 | Library. 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Amahi SPDY [![Build Status](https://travis-ci.org/amahi/spdy.png?branch=master)](https://travis-ci.org/amahi/spdy) [![GoDoc](https://godoc.org/github.com/amahi/spdy?status.svg)](https://godoc.org/github.com/amahi/spdy) [![Coverage Status](https://img.shields.io/coveralls/amahi/spdy.svg)](https://coveralls.io/r/amahi/spdy) 2 | ========== 3 | 4 | Test coverage (v1.1): 72% 5 | 6 | Amahi SPDY is a library built from scratch for building SPDY clients and servers in the Go programming language. It was meant to do it in a more "Go way" than other libraries available (which use mutexes liberally). Here is a high-level picture of how it's structured: 7 | 8 | ![SPDY Library Architecture](docs/img/spdy-arch.png) 9 | 10 | It supports a subset of [SPDY 3.1](http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft3-1). 11 | 12 | Check the online [documentation](http://godoc.org/github.com/amahi/spdy). 13 | 14 | This library is used in a streaming server/proxy implementation for Amahi, the [home and media server](https://www.amahi.org). 15 | 16 | Building a Server 17 | ======== 18 | 19 | ```go 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "github.com/amahi/spdy" 25 | "net/http" 26 | ) 27 | 28 | func handler(w http.ResponseWriter, r *http.Request) { 29 | fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:]) 30 | } 31 | 32 | func main() { 33 | http.HandleFunc("/", handler) 34 | 35 | //use spdy's Listen and serve 36 | err := spdy.ListenAndServe("localhost:4040",nil) 37 | if err != nil { 38 | //error handling here 39 | } 40 | } 41 | ``` 42 | 43 | Building a Client 44 | ======== 45 | 46 | ```go 47 | package main 48 | 49 | import ( 50 | "fmt" 51 | "github.com/amahi/spdy" 52 | "io" 53 | "net/http" 54 | ) 55 | 56 | func main() { 57 | //make a spdy client with a given address 58 | client, err := spdy.NewClient("localhost:4040") 59 | if err != nil { 60 | //handle error here 61 | } 62 | 63 | //make a request 64 | req, err := http.NewRequest("GET", "http://localhost:4040/banana", nil) 65 | if err != nil { 66 | //handle error here 67 | } 68 | 69 | //now send the request to obtain a http response 70 | res, err := client.Do(req) 71 | if err != nil { 72 | //something went wrong 73 | } 74 | 75 | //now handle the response 76 | data := make([]byte, int(res.ContentLength)) 77 | _, err = res.Body.(io.Reader).Read(data) 78 | fmt.Println(string(data)) 79 | res.Body.Close() 80 | } 81 | ``` 82 | Examples 83 | ======== 84 | 85 | We have [several examples](examples) to help in getting aqcuainted to the Amahi SPDY package. 86 | 87 | We also have a [reference implementation](https://github.com/amahi/spdy-proxy) of clients for the library, which contains an [origin server](https://github.com/amahi/spdy-proxy/blob/master/src/c/c.go), and a [proxy server](https://github.com/amahi/spdy-proxy/blob/master/src/p/p.go). 88 | 89 | Architecture 90 | ============ 91 | 92 | The library is divided in `Session` objects and `Stream` objects as far as the external interface. Each Session and Stream may have multiple goroutines and channels to manage their structure and communication patterns. Here is an overview diagram of how the pieces fit together: 93 | 94 | ![SPDY Library Architecture](docs/img/spdy-arch.png) 95 | 96 | Each Session controls the communication between two net.Conn connected endpoints. Each Session has a server loop and in it there are two goroutines, one for sending frames from the network connection and one for receiving frames from it. These two goroutines are designed to never block. Except of course if there are network issues, which break the Session and all Streams in the Session. 97 | 98 | Each Stream has a server and in it there are two goroutines, a Northbound Buffer Sender and a Control Flow Manager. The NorthBound Buffer Sender is in charge of writing data to the http response and causes control flow frames being sent southbound when data is written northbound. The Control Flow Manager is the owner of the control flow window size. 99 | 100 | In the end there are two copies of these stacks, one on each side of the connection. 101 | 102 | ![HTTP and SPDY](docs/img/end-to-end-http.png) 103 | 104 | The goals for the library are reliability, streaming and performance/scalability. 105 | 106 | 1) Design for reliability means that network connections are assumed to disconnect at any time, especially when it's most inapropriate for the library to handle. This also includes potential issues with bugs in within the library, so the library tries to handle all crazy errors in the most reasonable way. A client or a server built with this library should be able to run for months and months of reliable operation. It's not there yet, but it will be. 107 | 108 | 2) Streaming requests, unlike typical HTTP requests (which are short), require working with an arbitrary large number of open requests (streams) simultaneously, and most of them are flow-constrained at the client endpoint. Streaming clients kind of misbehave too, for example, they open and close many streams rapidly with `Range` request to check certain parts of the file. This is common with endpoint clients like [VLC](https://videolan.org/vlc/) or [Quicktime](https://www.apple.com/quicktime/) (Safari on iOS or Mac OS X). We wrote this library with the goal of making it not just suitable for HTTP serving, but also for streaming. 109 | 110 | 3) The library was built with performance and scalability in mind, so things have been done using as little blocking and copying of data as possible. It was meant to be implemented in the "go way", using concurrency extensively and channel communication. The library uses mutexes very sparingly so that handling of errors at all manner of inapropriate times becomes easier. It goes to great lengths to not block, establishing timeouts when network and even channel communication may fail. The library should use very very little CPU, even in the presence of many streams and sessions running simultaneously. 111 | 112 | This is not to say SPDY compliance/feature-completeness is not a priority. We're definitely interested in that, so that is an good area for contributions. 113 | 114 | 115 | Testing 116 | ======= 117 | 118 | The library needs more directed, white-box tests. Most of the testing has been black-box testing. It has been tested as follows: 119 | 120 | 1) Building a reference proxy and an origin server, exercising them with multiple streaming clients, stressing them with many streams in parallel. 121 | 122 | 2) The reference implementation above also contains some [integration tests](https://github.com/amahi/spdy-proxy/tree/master/integration-tests). These do not cover a lot in terms of stressing the library, but are a great baseline. 123 | 124 | As such, the integration tests should be considered more like sanity checks. We're interested in contributions that cover more and more edge cases! 125 | 126 | 3) We periorically run apps built with this library, with the [Go race detector](http://blog.golang.org/race-detector) enabled. We no longer found any more race conditions. 127 | 128 | We'd like to beef up the testing to make it scale! 129 | 130 | Code Coverage 131 | ====== 132 | 133 | To get a detailed report of covered code: 134 | ```sh 135 | go test -coverprofile=coverage.out && go tool cover -html=coverage.out -o coverage.html 136 | ``` 137 | 138 | Spec Coverage 139 | ====== 140 | 141 | A document detailing parts of spdy spec covered by the Amahi SPDY library can be found in [docs/specs](docs/specs). 142 | The document is labelled with the library version for which it is applicable. 143 | 144 | Status 145 | ====== 146 | 147 | Things implemented: 148 | * `SYN_STREAM`, `SYN_REPLY` and `RST_STREAM` frames 149 | * `WINDOW_UPDATE` and a (fixed) control flow window 150 | * `PING` frames 151 | * Support for other all types of HTTP requests 152 | * DATA frames, obviously 153 | * `GOAWAY` frame 154 | * NPN negotiation 155 | 156 | Things to be implemented: 157 | * Support for SETTINGS frames 158 | * Actual implementation of priorities (everything is one priority at the moment) 159 | * Server push 160 | * HEADERS frames 161 | * Variable flow control window size 162 | * Extensive error handling for all possible rainy-day scenarios specified in the specification 163 | * Support for pre-3.1 SPDY standards 164 | 165 | Contributing 166 | ============ 167 | 168 | * Fork it 169 | * Make changes, test them 170 | * Submit pull request! 171 | 172 | Credits 173 | ======= 174 | 175 | Credit goes to Jamie Hall for the patience and persistance to debug his excellent SPDY library that lead to the creation of this library. 176 | 177 | The library was started from scratch, but some isolated code like the header compression comes from Jamie's library as well as other libraries out there that we used for inspiration. The header dictionary table comes from the SPDY spec definition. 178 | 179 | The library has been extended by [Nilesh Jagnik](https://github.com/nileshjagnik) to support various new features along with the development of a Friendly API to create SPDY servers and clients. 180 | -------------------------------------------------------------------------------- /frame.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Frame related functions for generic for control/data frames 6 | // as well as specific frame related functions 7 | 8 | package spdy 9 | 10 | import ( 11 | "bytes" 12 | "encoding/binary" 13 | "fmt" 14 | "io" 15 | "strings" 16 | ) 17 | 18 | // ======================================== 19 | // Control Frames 20 | // ======================================== 21 | func (f controlFrame) Flags() frameFlags { return f.flags } 22 | func (f controlFrame) Data() []byte { return f.data } 23 | 24 | func (f controlFrame) String() string { 25 | s := fmt.Sprintf("Control frame %s, flags: %s, size: %d\n", f.kind, f.flags, len(f.data)) 26 | return s 27 | } 28 | 29 | func (f controlFrame) isFIN() bool { return f.flags&FLAG_FIN == FLAG_FIN } 30 | 31 | func (f frameFlags) String() string { 32 | if f == FLAG_NONE { 33 | return "-" 34 | } 35 | if f == FLAG_FIN { 36 | return "FIN" 37 | } 38 | return fmt.Sprintf("0x%02x", int(f)) 39 | } 40 | 41 | func (f controlFrameKind) String() string { 42 | switch f { 43 | case FRAME_SYN_STREAM: 44 | return "SYN_STREAM" 45 | case FRAME_SYN_REPLY: 46 | return "SYN_REPLY" 47 | case FRAME_RST_STREAM: 48 | return "RST_STREAM" 49 | case FRAME_SETTINGS: 50 | return "SETTINGS" 51 | case FRAME_PING: 52 | return "PING" 53 | case FRAME_GOAWAY: 54 | return "GOAWAY" 55 | case FRAME_HEADERS: 56 | return "HEADERS" 57 | case FRAME_WINDOW_UPDATE: 58 | return "WINDOW_UPDATE" 59 | } 60 | return fmt.Sprintf("Type(%#04x)", uint16(f)) 61 | } 62 | 63 | func (f controlFrame) Write(w io.Writer) (n int64, err error) { 64 | 65 | total := len(f.data) + 8 66 | debug.Printf("Writing control frame %s, flags: %s, payload: %d", f.kind, f.flags, len(f.data)) 67 | nn, err := writeFrame(w, []interface{}{uint16(0x8000) | uint16(0x0003), f.kind, f.flags}, f.data) 68 | if nn != total { 69 | log.Println("WARNING in controlFrame.Write, wrote", nn, "vs. frame size", total) 70 | } 71 | return int64(nn), err 72 | } 73 | 74 | // read the stream ID from the payload 75 | // applies to SYN_STREAM, SYN_REPLY, RST_STREAM, GOAWAY, HEADERS and WINDOWS_UPDATE 76 | func (f *controlFrame) streamID() (id streamID) { 77 | data := bytes.NewBuffer(f.data[0:4]) 78 | err := binary.Read(data, binary.BigEndian, &id) 79 | if err != nil { 80 | log.Println("ERROR: Cannot read stream ID from a control frame that is supposed to have a stream ID:", err) 81 | id = 0 82 | return 83 | } 84 | id &= 0x7fffffff 85 | 86 | return 87 | } 88 | 89 | // ======================================== 90 | // Data Frames 91 | // ======================================== 92 | func (f dataFrame) Flags() frameFlags { return f.flags } 93 | func (f dataFrame) Data() []byte { return f.data } 94 | func (f dataFrame) isFIN() bool { return f.flags&FLAG_FIN == FLAG_FIN } 95 | 96 | func (f dataFrame) Write(w io.Writer) (n int64, err error) { 97 | total := len(f.data) + 8 98 | debug.Printf("Writing data frame, flags: %s, size: %d", f.flags, len(f.data)) 99 | nn, err := writeFrame(w, []interface{}{f.stream & 0x7fffffff, f.flags}, f.data) 100 | if nn != total { 101 | log.Println("WARNING in dataFrame.Write, wrote", nn, "vs. frame size", total) 102 | } 103 | return int64(nn), err 104 | } 105 | 106 | func (f dataFrame) String() string { 107 | l := len(f.data) 108 | s := fmt.Sprintf("\n\tFrame: DATA of size %d, for stream #%d", l, f.stream) 109 | s += fmt.Sprintf(", Flags: %s", f.flags) 110 | if l > 16 { 111 | s += fmt.Sprintf("\n\tData: [%x .. %x]", f.data[0:6], f.data[l-6:]) 112 | } else if l > 0 { 113 | s += fmt.Sprintf("\n\tData: [%x]", f.data) 114 | } 115 | return s 116 | } 117 | 118 | // ======================================== 119 | // Generic frame-writing utilities 120 | // ======================================== 121 | 122 | func writeFrame(w io.Writer, head []interface{}, data []byte) (n int, err error) { 123 | var nn int 124 | // Header (40 bits) 125 | err = writeBinary(w, head...) 126 | if err != nil { 127 | return 128 | } 129 | n += 5 // frame head, in bytes, without the length field 130 | 131 | // Length (24 bits) 132 | length := len(data) 133 | nn, err = w.Write([]byte{ 134 | byte(length & 0x00ff0000 >> 16), 135 | byte(length & 0x0000ff00 >> 8), 136 | byte(length & 0x000000ff), 137 | }) 138 | n += nn 139 | if err != nil { 140 | log.Println("Write of length failed:", err) 141 | return 142 | } 143 | // Data 144 | if length > 0 { 145 | nn, err = w.Write(data) 146 | if err != nil { 147 | log.Println("Write of data failed:", err) 148 | return 149 | } 150 | n += nn 151 | } 152 | return 153 | } 154 | 155 | func writeBinary(r io.Writer, args ...interface{}) (err error) { 156 | for _, a := range args { 157 | err = binary.Write(r, binary.BigEndian, a) 158 | if err != nil { 159 | return 160 | } 161 | } 162 | return 163 | } 164 | 165 | // ======================================== 166 | // Generic frame-reading utilities 167 | // ======================================== 168 | 169 | // readFrame reads an entire frame into memory 170 | func readFrame(r io.Reader) (f frame, err error) { 171 | headBuffer := new(bytes.Buffer) 172 | _, err = io.CopyN(headBuffer, r, 5) 173 | if err != nil { 174 | return 175 | } 176 | if headBuffer.Bytes()[0]&0x80 == 0 { 177 | // Data 178 | df := dataFrame{} 179 | err = readBinary(headBuffer, &df.stream, &df.flags) 180 | if err != nil { 181 | return 182 | } 183 | df.data, err = readData(r) 184 | f = df 185 | } else { 186 | // Control 187 | cf := controlFrame{} 188 | headBuffer.ReadByte() // FIXME skip version word 189 | headBuffer.ReadByte() 190 | err = readBinary(headBuffer, &cf.kind, &cf.flags) 191 | if err != nil { 192 | return 193 | } 194 | cf.data, err = readData(r) 195 | f = cf 196 | } 197 | return 198 | } 199 | 200 | func readBinary(r io.Reader, args ...interface{}) (err error) { 201 | for _, a := range args { 202 | err = binary.Read(r, binary.BigEndian, a) 203 | if err != nil { 204 | return 205 | } 206 | } 207 | return 208 | } 209 | 210 | func readData(r io.Reader) (data []byte, err error) { 211 | lengthField := make([]byte, 3) 212 | _, err = io.ReadFull(r, lengthField) 213 | if err != nil { 214 | return 215 | } 216 | var length uint32 217 | length |= uint32(lengthField[0]) << 16 218 | length |= uint32(lengthField[1]) << 8 219 | length |= uint32(lengthField[2]) 220 | 221 | if length > 0 { 222 | data = make([]byte, int(length)) 223 | _, err = io.ReadFull(r, data) 224 | if err != nil { 225 | data = nil 226 | return 227 | } 228 | } else { 229 | data = []byte{} 230 | } 231 | return 232 | } 233 | 234 | // ======================================== 235 | // SYN_STREAM frame 236 | // ======================================== 237 | 238 | func (frame frameSynStream) Flags() frameFlags { 239 | return frame.flags 240 | } 241 | 242 | func (frame frameSynStream) Data() []byte { 243 | buf := new(bytes.Buffer) 244 | // stream-id 245 | binary.Write(buf, binary.BigEndian, frame.stream&0x7fffffff) 246 | // associated-to-stream-id FIXME in the long term 247 | binary.Write(buf, binary.BigEndian, frame.associated_stream&0x7fffffff) 248 | // Priority & unused/reserved 249 | var misc uint16 = uint16((frame.priority & 0x7) << 13) 250 | binary.Write(buf, binary.BigEndian, misc) 251 | // debug.Println("Before header:", buf.Bytes()) 252 | frame.session.headerWriter.writeHeader(buf, frame.header) 253 | // debug.Println("Compressed header:", buf.Bytes()) 254 | return buf.Bytes() 255 | } 256 | 257 | func (frame frameSynStream) Write(w io.Writer) (n int64, err error) { 258 | f := controlFrame{kind: FRAME_SYN_STREAM, flags: frame.flags, data: frame.Data()} 259 | return f.Write(w) 260 | } 261 | 262 | // print details of the frame to a string 263 | func (frame frameSynStream) String() string { 264 | s := fmt.Sprintf("\n\tFrame: SYN_STREAM, Stream #%d", frame.stream) 265 | s += fmt.Sprintf(", Flags: %s", frame.flags) 266 | s += fmt.Sprintf("\n\tHeaders:\n") 267 | for i := range frame.header { 268 | s += fmt.Sprintf("\t\t%s: %s\n", i, strings.Join(frame.header[i], ", ")) 269 | } 270 | return s 271 | } 272 | 273 | // ======================================== 274 | // SYN_REPLY frame 275 | // ======================================== 276 | 277 | func (frame frameSynReply) Flags() frameFlags { 278 | return frame.flags 279 | } 280 | 281 | func (frame frameSynReply) Data() []byte { 282 | buf := new(bytes.Buffer) 283 | binary.Write(buf, binary.BigEndian, frame.stream&0x7fffffff) 284 | frame.session.headerWriter.writeHeader(buf, frame.headers) 285 | return buf.Bytes() 286 | } 287 | 288 | func (frame frameSynReply) Write(w io.Writer) (n int64, err error) { 289 | cf := controlFrame{kind: FRAME_SYN_REPLY, data: frame.Data()} 290 | return cf.Write(w) 291 | } 292 | 293 | // print details of the frame to a string 294 | func (frame frameSynReply) String() string { 295 | s := fmt.Sprintf("\n\tFrame: SYN_REPLY, Stream #%d", frame.stream) 296 | s += fmt.Sprintf(", Flags: %s", frame.flags) 297 | s += fmt.Sprintf("\n\tHeaders:\n") 298 | for i := range frame.headers { 299 | s += fmt.Sprintf("\t\t%s: %s\n", i, strings.Join(frame.headers[i], ", ")) 300 | } 301 | return s 302 | } 303 | 304 | // ======================================== 305 | // SETTINGS frame 306 | // ======================================== 307 | 308 | func (s settings) Flags() frameFlags { 309 | return s.flags 310 | } 311 | 312 | func (s settings) Data() []byte { 313 | buf := new(bytes.Buffer) 314 | binary.Write(buf, binary.BigEndian, s.count) 315 | for i := range s.svp { 316 | binary.Write(buf, binary.BigEndian, s.svp[i].flags) 317 | binary.Write(buf, binary.BigEndian, s.svp[i].id) 318 | binary.Write(buf, binary.BigEndian, s.svp[i].value) 319 | } 320 | return buf.Bytes() 321 | } 322 | 323 | func (s settings) Write(w io.Writer) (n int64, err error) { 324 | cf := controlFrame{kind: FRAME_SETTINGS, flags: s.flags, data: s.Data()} 325 | return cf.Write(w) 326 | } 327 | 328 | func (s settings) String() (r string) { 329 | r = fmt.Sprintf("\n\tFrame: SETTINGS, Flags: %s\n", s.flags) 330 | r += fmt.Sprintf("\tValue/Pairs (%d):\n", s.count) 331 | for i := range s.svp { 332 | r += fmt.Sprintf("\t\t%d: %d\tflags: %d\n", s.svp[i].id, s.svp[i].value, s.svp[i].flags) 333 | } 334 | return 335 | } 336 | 337 | // ======================================== 338 | // WINDOW_UPDATE frame 339 | // ======================================== 340 | 341 | // takes a frame and the delta window size and returns a WINDOW_UPDATE frame 342 | func windowUpdateFor(id streamID, dws int) frame { 343 | 344 | data := new(bytes.Buffer) 345 | binary.Write(data, binary.BigEndian, id) 346 | binary.Write(data, binary.BigEndian, uint32(dws)) 347 | 348 | return controlFrame{kind: FRAME_WINDOW_UPDATE, data: data.Bytes()} 349 | } 350 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // This file contains header-handling functions 6 | // The header dictionary came from the SPDY standard. 7 | // Some of the functions came from Jamie Hall's 8 | // SPDY go library. 9 | 10 | package spdy 11 | 12 | import ( 13 | "bytes" 14 | "compress/zlib" 15 | "encoding/binary" 16 | "io" 17 | "net/http" 18 | "strings" 19 | "sync" 20 | ) 21 | 22 | type hrSource struct { 23 | r io.Reader 24 | m sync.RWMutex 25 | c *sync.Cond 26 | } 27 | 28 | func (src *hrSource) Read(p []byte) (n int, err error) { 29 | src.m.RLock() 30 | for src.r == nil { 31 | src.c.Wait() 32 | } 33 | n, err = src.r.Read(p) 34 | src.m.RUnlock() 35 | if err == io.EOF { 36 | src.change(nil) 37 | err = nil 38 | } 39 | return 40 | } 41 | 42 | func (src *hrSource) change(r io.Reader) { 43 | src.m.Lock() 44 | defer src.m.Unlock() 45 | src.r = r 46 | src.c.Broadcast() 47 | } 48 | 49 | // A headerReader reads zlib-compressed headers from discontiguous sources. 50 | type headerReader struct { 51 | source hrSource 52 | decompressor io.ReadCloser 53 | } 54 | 55 | // newHeaderReader creates a headerReader with the initial dictionary. 56 | func newHeaderReader() (hr *headerReader) { 57 | hr = new(headerReader) 58 | hr.source.c = sync.NewCond(hr.source.m.RLocker()) 59 | return 60 | } 61 | 62 | // ReadHeader reads a set of headers from a reader. 63 | func (hr *headerReader) readHeader(r io.Reader) (h http.Header, err error) { 64 | hr.source.change(r) 65 | h, err = hr.read() 66 | return 67 | } 68 | 69 | // Decode reads a set of headers from a block of bytes. 70 | func (hr *headerReader) decode(data []byte) (h http.Header, err error) { 71 | hr.source.change(bytes.NewBuffer(data)) 72 | h, err = hr.read() 73 | return 74 | } 75 | 76 | func (hr *headerReader) read() (h http.Header, err error) { 77 | var count uint32 78 | if hr.decompressor == nil { 79 | hr.decompressor, err = zlib.NewReaderDict(&hr.source, headerDictionary) 80 | if err != nil { 81 | return 82 | } 83 | } 84 | err = binary.Read(hr.decompressor, binary.BigEndian, &count) 85 | if err != nil { 86 | return 87 | } 88 | h = make(http.Header, int(count)) 89 | for i := 0; i < int(count); i++ { 90 | var name, value string 91 | name, err = readHeaderString(hr.decompressor) 92 | if err != nil { 93 | return 94 | } 95 | value, err = readHeaderString(hr.decompressor) 96 | if err != nil { 97 | return 98 | } 99 | valueList := strings.Split(string(value), "\x00") 100 | for _, v := range valueList { 101 | h.Add(name, v) 102 | } 103 | } 104 | return 105 | } 106 | 107 | func readHeaderString(r io.Reader) (s string, err error) { 108 | var length uint32 109 | err = binary.Read(r, binary.BigEndian, &length) 110 | if err != nil { 111 | return 112 | } 113 | data := make([]byte, int(length)) 114 | _, err = io.ReadFull(r, data) 115 | if err != nil { 116 | return 117 | } 118 | return string(data), nil 119 | } 120 | 121 | // write zlib-compressed headers on different streams 122 | type headerWriter struct { 123 | compressor *zlib.Writer 124 | buffer *bytes.Buffer 125 | } 126 | 127 | // creates a headerWriter ready to compress headers 128 | func newHeaderWriter() (hw *headerWriter) { 129 | hw = &headerWriter{buffer: new(bytes.Buffer)} 130 | hw.compressor, _ = zlib.NewWriterLevelDict(hw.buffer, zlib.BestCompression, headerDictionary) 131 | return 132 | } 133 | 134 | // write a header block directly to a writer 135 | func (hw *headerWriter) writeHeader(w io.Writer, h http.Header) (err error) { 136 | hw.write(h) 137 | _, err = io.Copy(w, hw.buffer) 138 | hw.buffer.Reset() 139 | return 140 | } 141 | 142 | // Encode returns a compressed header block. 143 | func (hw *headerWriter) encode(h http.Header) (data []byte) { 144 | hw.write(h) 145 | data = make([]byte, hw.buffer.Len()) 146 | hw.buffer.Read(data) 147 | return 148 | } 149 | 150 | func (hw *headerWriter) write(h http.Header) { 151 | binary.Write(hw.compressor, binary.BigEndian, uint32(len(h))) 152 | for k, vals := range h { 153 | k = strings.ToLower(k) 154 | binary.Write(hw.compressor, binary.BigEndian, uint32(len(k))) 155 | binary.Write(hw.compressor, binary.BigEndian, []byte(k)) 156 | v := strings.Join(vals, "\x00") 157 | binary.Write(hw.compressor, binary.BigEndian, uint32(len(v))) 158 | binary.Write(hw.compressor, binary.BigEndian, []byte(v)) 159 | } 160 | hw.compressor.Flush() 161 | } 162 | 163 | // compression header for SPDY/3 164 | var headerDictionary = []byte{ 165 | 0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, 166 | 0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, 167 | 0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, 168 | 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, 169 | 0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, 170 | 0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, 171 | 0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, 172 | 0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, 173 | 0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, 174 | 0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 175 | 0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, 176 | 0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, 177 | 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, 178 | 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, 179 | 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, 180 | 0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, 181 | 0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 182 | 0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, 183 | 0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 184 | 0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, 185 | 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 186 | 0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, 187 | 0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, 188 | 0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, 189 | 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 190 | 0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 191 | 0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, 192 | 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, 193 | 0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, 194 | 0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 195 | 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 196 | 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 197 | 0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, 198 | 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 199 | 0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 200 | 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, 201 | 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 202 | 0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 203 | 0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, 204 | 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 205 | 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 206 | 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 207 | 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, 208 | 0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, 209 | 0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, 210 | 0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 211 | 0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, 212 | 0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, 213 | 0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, 214 | 0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, 215 | 0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 216 | 0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, 217 | 0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, 218 | 0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, 219 | 0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, 220 | 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, 221 | 0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, 222 | 0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, 223 | 0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 224 | 0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, 225 | 0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, 226 | 0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, 227 | 0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, 228 | 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 229 | 0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, 230 | 0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, 231 | 0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, 232 | 0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, 233 | 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 234 | 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, 235 | 0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, 236 | 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 237 | 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, 238 | 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, 239 | 0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, 240 | 0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, 241 | 0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, 242 | 0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 243 | 0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, 244 | 0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, 245 | 0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, 246 | 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, 247 | 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, 248 | 0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, 249 | 0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, 250 | 0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, 251 | 0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, 252 | 0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, 253 | 0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, 254 | 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, 255 | 0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 256 | 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 257 | 0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 258 | 0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, 259 | 0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 260 | 0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, 261 | 0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, 262 | 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 263 | 0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 264 | 0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, 265 | 0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, 266 | 0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, 267 | 0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, 268 | 0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, 269 | 0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, 270 | 0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 271 | 0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, 272 | 0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, 273 | 0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, 274 | 0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, 275 | 0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, 276 | 0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, 277 | 0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, 278 | 0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, 279 | 0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, 280 | 0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, 281 | 0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, 282 | 0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, 283 | 0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, 284 | 0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, 285 | 0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 286 | 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, 287 | 0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, 288 | 0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, 289 | 0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, 290 | 0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, 291 | 0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, 292 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, 293 | 0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, 294 | 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, 295 | 0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, 296 | 0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, 297 | 0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, 298 | 0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, 299 | 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, 300 | 0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, 301 | 0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, 302 | 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 303 | 0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, 304 | 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 305 | 0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 306 | 0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, 307 | 0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, 308 | 0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, 309 | 0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, 310 | 0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, 311 | 0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, 312 | 0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, 313 | 0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, 314 | 0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, 315 | 0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, 316 | 0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, 317 | 0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, 318 | 0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, 319 | 0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 320 | 0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, 321 | 0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, 322 | 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, 323 | 0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, 324 | 0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, 325 | 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 326 | 0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, 327 | 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, 328 | 0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, 329 | 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 330 | 0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, 331 | 0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, 332 | 0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, 333 | 0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 334 | 0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, 335 | 0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, 336 | 0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, 337 | 0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 338 | 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, 339 | 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, 340 | 0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, 341 | 0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, 342 | 0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e, 343 | } 344 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Session related functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "net" 16 | "net/http" 17 | "sync/atomic" 18 | "time" 19 | ) 20 | 21 | // NewServerSession creates a new Session with the given network connection. 22 | // This Session should be used as a server, and the given http.Server will be 23 | // used to serve requests arriving. The user should call Serve() once it's 24 | // ready to start serving. New streams will be created as per the SPDY 25 | // protocol. 26 | func NewServerSession(conn net.Conn, server *http.Server) *Session { 27 | s := &Session{ 28 | conn: conn, 29 | out: make(chan frame), 30 | in: make(chan frame), 31 | new_stream: make(chan *Stream), 32 | end_stream: make(chan *Stream), 33 | server: server, 34 | headerWriter: newHeaderWriter(), 35 | headerReader: newHeaderReader(), 36 | nextStream: 2, 37 | nextPing: 2, 38 | streams: make(map[streamID]*Stream), 39 | pinger: make(chan uint32), 40 | } 41 | 42 | return s 43 | } 44 | 45 | // NewClientSession creates a new Session that should be used as a client. 46 | // the given http.Server will be used to serve requests arriving. The user 47 | // should call Serve() once it's ready to start serving. New streams will be 48 | // created as per the SPDY protocol. 49 | func NewClientSession(conn net.Conn) *Session { 50 | s := &Session{ 51 | conn: conn, 52 | out: make(chan frame), 53 | in: make(chan frame), 54 | new_stream: make(chan *Stream), 55 | end_stream: make(chan *Stream), 56 | server: nil, 57 | headerWriter: newHeaderWriter(), 58 | headerReader: newHeaderReader(), 59 | nextStream: 1, 60 | nextPing: 1, 61 | streams: make(map[streamID]*Stream), 62 | pinger: make(chan uint32), 63 | } 64 | 65 | return s 66 | } 67 | 68 | // Serve starts serving a Session. This implementation of Serve only returns 69 | // when there has been an error condition. 70 | func (s *Session) Serve() (err error) { 71 | 72 | debug.Println("Session server started") 73 | 74 | receiver_done := make(chan bool) 75 | sender_done := make(chan bool) 76 | 77 | // start frame sender 78 | go s.frameSender(sender_done, s.out) 79 | 80 | // start frame receiver 81 | go s.frameReceiver(receiver_done, s.in) 82 | 83 | // start serving loop 84 | err = s.session_loop(sender_done, receiver_done) 85 | if err != nil { 86 | log.Printf("ERROR: %s", netErrorString(err)) 87 | } 88 | 89 | // force removing all existing streams 90 | for i := range s.streams { 91 | str := s.streams[i] 92 | str.finish_stream() 93 | delete(s.streams, i) 94 | } 95 | 96 | // close this session 97 | s.Close() 98 | debug.Println("Session closed. Session server done.") 99 | 100 | return 101 | } 102 | 103 | func (s *Session) session_loop(sender_done, receiver_done <-chan bool) (err error) { 104 | for { 105 | select { 106 | case f := <-s.in: 107 | // received a frame 108 | switch frame := f.(type) { 109 | case controlFrame: 110 | err = s.processControlFrame(frame) 111 | case dataFrame: 112 | err = s.processDataFrame(frame) 113 | } 114 | if err != nil { 115 | return 116 | } 117 | case ns, ok := <-s.new_stream: 118 | // registering a new stream for this session 119 | if ok { 120 | s.streams[ns.id] = ns 121 | } else { 122 | return 123 | } 124 | case os, ok := <-s.end_stream: 125 | // unregistering a stream from this session 126 | if ok { 127 | delete(s.streams, os.id) 128 | } else { 129 | return 130 | } 131 | case _, _ = <-receiver_done: 132 | debug.Println("Session receiver is done") 133 | return 134 | case _, _ = <-sender_done: 135 | debug.Println("Session sender is done") 136 | return 137 | } 138 | } 139 | } 140 | 141 | // Close closes the Session and the underlaying network connection. 142 | // It should be called when the Session is idle for best results. 143 | func (s *Session) Close() { 144 | // FIXME - what else do we need to do here? 145 | if s.closed { 146 | debug.Println("WARNING: session was already closed - why?") 147 | return 148 | } 149 | 150 | s.closed = true 151 | 152 | // in case any of the closes below clashes 153 | defer no_panics() 154 | 155 | close(s.out) 156 | close(s.in) 157 | close(s.pinger) 158 | 159 | debug.Println("Closing the network connection") 160 | s.conn.Close() 161 | } 162 | 163 | // return the next stream id 164 | func (s *Session) nextStreamID() streamID { 165 | return (streamID)(atomic.AddUint32((*uint32)(&s.nextStream), 2) - 2) 166 | } 167 | 168 | // frameSender takes a channel and gets each of the frames coming from 169 | // it and sends them down the session connection, until the channel 170 | // is closed or there are errors in sending over the network 171 | func (s *Session) frameSender(done chan<- bool, in <-chan frame) { 172 | for f := range in { 173 | s.conn.SetWriteDeadline(time.Now().Add(5 * time.Second)) 174 | _, err := f.Write(s.conn) 175 | if err != nil { 176 | log.Println("ERROR in frameSender.Write:", err) 177 | break 178 | } 179 | } 180 | done <- true 181 | debug.Printf("Session sender ended") 182 | } 183 | 184 | // frameReceiver takes a channel and receives frames, sending them to 185 | // the network connection until there is an error 186 | func (s *Session) frameReceiver(done chan<- bool, incoming chan<- frame) { 187 | defer no_panics() 188 | 189 | for { 190 | frame, err := readFrame(s.conn) 191 | if err == io.EOF { 192 | // normal reasons, like disconnection, etc. 193 | break 194 | } 195 | if err != nil { 196 | // some other communication error 197 | log.Printf("WARN: communication error: %s", netErrorString(err)) 198 | break 199 | } 200 | // ship the frame upstream -- this must be ensured to not block 201 | debug.Printf("Session got: %s", frame) 202 | incoming <- frame 203 | } 204 | done <- true 205 | debug.Printf("Session receiver ended") 206 | } 207 | func (s *Session) processControlFrame(frame controlFrame) (err error) { 208 | 209 | switch frame.kind { 210 | case FRAME_SYN_STREAM: 211 | err = s.processSynStream(frame) 212 | select { 213 | case ns, ok := <-s.new_stream: 214 | // registering a new stream for this session 215 | if ok { 216 | s.streams[ns.id] = ns 217 | } else { 218 | return 219 | } 220 | } 221 | controlflag := 0 222 | // for non-FIN frames wait for data frames 223 | if !frame.isFIN() { 224 | for controlflag == 0 { 225 | deadline := time.After(3 * time.Second) 226 | select { 227 | case f := <-s.in: 228 | // received a frame 229 | switch fr := f.(type) { 230 | case dataFrame: 231 | //process data frames 232 | err = s.processDataFrame(fr) 233 | if fr.isFIN() { 234 | controlflag = 1 235 | } 236 | break 237 | case controlFrame: 238 | err = s.processControlFrame(fr) 239 | 240 | } 241 | if err != nil { 242 | return 243 | } 244 | break 245 | case <-deadline: 246 | //unsuccessfully waited for FIN 247 | debug.Println("Waited long enough but no data frames recieved") 248 | controlflag = 2 249 | break 250 | } 251 | } 252 | } 253 | return 254 | case FRAME_SYN_REPLY: 255 | return s.processSynReply(frame) 256 | case FRAME_SETTINGS: 257 | s.processSettings(frame) 258 | return nil 259 | case FRAME_RST_STREAM: 260 | // just to avoid locking issues, send it in a goroutine 261 | go s.processRstStream(frame) 262 | case FRAME_PING: 263 | return s.processPing(frame) 264 | case FRAME_WINDOW_UPDATE: 265 | s.processWindowUpdate(frame) 266 | case FRAME_GOAWAY: 267 | s.processGoaway(frame) 268 | case FRAME_HEADERS: 269 | panic("FIXME HEADERS") 270 | } 271 | 272 | return 273 | } 274 | func (s *Session) SendGoaway(f frameFlags, dat []byte) { 275 | s.out <- controlFrame{kind: FRAME_GOAWAY, flags: f, data: dat} 276 | } 277 | 278 | func (s *Session) processGoaway(frame controlFrame) { 279 | if len(frame.data) != 8 { 280 | log.Println("ERROR: could not process goaway: Frame should be 8 bits long") 281 | return 282 | } 283 | status_code := bytes.NewBuffer(frame.data[4:8]) 284 | var status int32 285 | err := binary.Read(status_code, binary.BigEndian, &status) 286 | if err != nil { 287 | log.Println("ERROR: Cannot read status code from a goaway frame:", err) 288 | return 289 | } 290 | 291 | lst_id := frame.streamID() 292 | debug.Printf("GOAWAY Frame recieved, Last-good-stream-ID: %d, Status Code: %d", lst_id, status) 293 | 294 | //to check if some stream with ID < Last-good-stream-ID is open 295 | closeSessionFlag := 0 296 | 297 | //Start going away 298 | s.goaway_recvd = true 299 | 300 | //Close streams with ID > Last-good-stream-ID 301 | for id, st := range s.streams { 302 | if id > lst_id { 303 | if !st.closed { 304 | st.finish_stream() 305 | delete(s.streams, id) 306 | } 307 | } else { 308 | if !st.closed { 309 | closeSessionFlag = 1 310 | } 311 | } 312 | } 313 | 314 | // Close Session if no remaining streams 315 | if closeSessionFlag == 0 { 316 | // maybe close the session? Even if session is not closed from this end, the sender will close it from the other end 317 | } 318 | } 319 | 320 | func (s *Session) processDataFrame(frame dataFrame) (err error) { 321 | stream, found := s.streams[frame.stream] 322 | if !found { 323 | // no error because this could happen if a stream is closed with outstanding data 324 | debug.Printf("WARN: stream %d not found", frame.stream) 325 | return 326 | } 327 | // send it to the stream for processing. this BETTER NOT BLOCK! 328 | deadline := time.After(300 * time.Millisecond) 329 | select { 330 | case stream.data <- frame: 331 | // send this data frame to the corresponding stream 332 | case <-deadline: 333 | // maybe it closed just before we tried to send it 334 | debug.Printf("Stream #%d: session timed out while sending northbound data", stream.id) 335 | } 336 | 337 | return 338 | } 339 | 340 | func (s *Session) processSynStream(frame controlFrame) (err error) { 341 | _, err = s.newServerStream(frame) 342 | if err != nil { 343 | log.Printf(fmt.Sprintf("cannot create syn stream frame: %s", err)) 344 | return 345 | } 346 | 347 | return 348 | } 349 | 350 | func (s *Session) processSynReply(frame controlFrame) (err error) { 351 | 352 | debug.Println("Processing SYN_REPLY received") 353 | id := frame.streamID() 354 | if id == 0 { 355 | err = errors.New("Invalid stream ID 0 received") 356 | return 357 | } 358 | 359 | stream, ok := s.streams[id] 360 | if !ok { 361 | err = errors.New(fmt.Sprintf("Stream with ID %d not found", id)) 362 | log.Printf("ERROR: %s", err) 363 | return 364 | } 365 | 366 | // send this control frame to the corresponding stream 367 | stream.control <- frame 368 | return 369 | } 370 | 371 | // Read details for SETTINGS frame 372 | //FIXME : shows error unexpected EOF when communicating with firefox 373 | func (s *Session) processSettings(frame controlFrame) (err error) { 374 | s.settings = new(settings) 375 | data := bytes.NewBuffer(frame.data) 376 | err = binary.Read(data, binary.BigEndian, &s.settings.count) 377 | if err != nil { 378 | return 379 | } 380 | s.settings.svp = make([]settingsValuePairs, s.settings.count) 381 | for i := uint32(0); i < s.settings.count; i++ { 382 | err = binary.Read(data, binary.BigEndian, &s.settings.svp[i].flags) 383 | if err != nil { 384 | return 385 | } 386 | err = binary.Read(data, binary.BigEndian, &s.settings.svp[i].id) 387 | if err != nil { 388 | return 389 | } 390 | err = binary.Read(data, binary.BigEndian, &s.settings.svp[i].value) 391 | if err != nil { 392 | return 393 | } 394 | } 395 | 396 | return 397 | } 398 | 399 | func (s *Session) processRstStream(frame controlFrame) { 400 | 401 | debug.Println("Processing RST_STREAM received") 402 | id := frame.streamID() 403 | if id == 0 { 404 | log.Printf("Session: invalid stream ID 0 received") 405 | return 406 | } 407 | 408 | stream, ok := s.streams[id] 409 | if !ok || (ok && stream.closed) { 410 | debug.Printf("Window update for unknown stream #%d ignored", id) 411 | debug.Println("known streams are", s.streams) 412 | return 413 | } 414 | 415 | // send this control frame to the corresponding stream 416 | stream.control <- frame 417 | } 418 | 419 | // Read details for PING frame 420 | func (s *Session) processPing(frame controlFrame) (err error) { 421 | s.settings = new(settings) 422 | var id uint32 423 | data := bytes.NewBuffer(frame.data[0:4]) 424 | binary.Read(data, binary.BigEndian, &id) 425 | debug.Printf("PING #%d", id) 426 | 427 | // check that it's initiated by this end or the other 428 | if (s.nextPing & 0x00000001) == (uint32(id) & 0x00000001) { 429 | // the ping received matches our partity, do not reply! 430 | select { 431 | case s.pinger <- id: 432 | // Pingback received successfully 433 | default: 434 | // noone was listening 435 | debug.Println("Pingback discarded (received too late)") 436 | } 437 | return 438 | } 439 | 440 | // send it right back! 441 | s.out <- frame 442 | 443 | return 444 | } 445 | 446 | func no_panics() { 447 | if v := recover(); v != nil { 448 | debug.Println("Got a panic:", v) 449 | } 450 | } 451 | 452 | // Ping issues a SPDY PING frame and returns true if it the other side returned 453 | // the PING frame within the duration, else it returns false. NOTE only one 454 | // outstanting ping works in the current implementation. 455 | func (s *Session) Ping(d time.Duration) (pinged bool) { 456 | 457 | // increase the next ping id 458 | id := atomic.AddUint32((*uint32)(&s.nextPing), 2) - 2 459 | 460 | data := new(bytes.Buffer) 461 | binary.Write(data, binary.BigEndian, id) 462 | 463 | ping := controlFrame{kind: FRAME_PING, flags: 0, data: data.Bytes()} 464 | 465 | defer no_panics() 466 | 467 | s.out <- ping 468 | 469 | pinged = false 470 | 471 | select { 472 | case pid, ok := <-s.pinger: 473 | if ok { // make sure we get the same id we sent back 474 | if pid == id { 475 | pinged = true 476 | } 477 | } 478 | case <-time.After(d): 479 | debug.Printf("Pingback timed out") 480 | // timeout 481 | } 482 | 483 | return pinged 484 | } 485 | 486 | func (s *Session) processWindowUpdate(frame controlFrame) { 487 | 488 | id := frame.streamID() 489 | if id == 0 { 490 | // FIXME - rather than panic, just issue a warning, since some 491 | // browsers will trigger the panic naturally 492 | log.Println("WARNING: no support for session flow control yet") 493 | } 494 | 495 | stream, ok := s.streams[id] 496 | if !ok { 497 | debug.Printf("Window update for unknown stream #%d ignored", id) 498 | return 499 | } 500 | if stream.closed { 501 | debug.Printf("Window update for closed stream #%d ignored", id) 502 | debug.Println("known streams are", s.streams) 503 | return 504 | } 505 | 506 | // just to avoid locking issues, send it in a goroutine, and put a deadline 507 | go func() { 508 | deadline := time.After(1200 * time.Millisecond) 509 | select { 510 | case stream.control <- frame: 511 | // send this control frame to the corresponding stream 512 | case <-deadline: 513 | // maybe it closed just before we tried to send it 514 | debug.Printf("Stream #%d: session timed out while sending %s north", stream.id, frame) 515 | } 516 | }() 517 | } 518 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013-14, Amahi. All rights reserved. 2 | // Use of this source code is governed by the 3 | // license that can be found in the LICENSE file. 4 | 5 | // Stream related functions 6 | 7 | package spdy 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | "errors" 13 | "fmt" 14 | "io" 15 | "net/http" 16 | "net/url" 17 | "runtime" 18 | "strconv" 19 | "strings" 20 | "time" 21 | ) 22 | 23 | const INITIAL_FLOW_CONTOL_WINDOW int32 = 64 * 1024 24 | const NORTHBOUND_SLOTS = 5 25 | 26 | // NewClientStream starts a new Stream (in the given Session), to be used as a client 27 | func (s *Session) NewClientStream() *Stream { 28 | // no stream creation after goaway has been recieved 29 | if !s.goaway_recvd { 30 | str := &Stream{ 31 | id: s.nextStreamID(), 32 | session: s, 33 | priority: 4, // FIXME need to implement priorities 34 | associated_stream: 0, // FIXME for pushes we need to implement it 35 | control: make(chan controlFrame), 36 | data: make(chan dataFrame), 37 | response: make(chan bool), 38 | eos: make(chan bool), 39 | stop_server: make(chan bool), 40 | flow_req: make(chan int32, 1), 41 | flow_add: make(chan int32, 1), 42 | upstream_buffer: make(chan upstream_data, NORTHBOUND_SLOTS), 43 | } 44 | 45 | go str.serve() 46 | 47 | go str.northboundBufferSender() 48 | 49 | go str.flowManager(INITIAL_FLOW_CONTOL_WINDOW, str.flow_add, str.flow_req) 50 | 51 | // add the stream to the session 52 | 53 | deadline := time.After(1500 * time.Millisecond) 54 | select { 55 | case s.new_stream <- str: 56 | // done 57 | return str 58 | case <-deadline: 59 | // somehow it was locked 60 | debug.Printf("Stream #%d: cannot be created. Stream is hung. Resetting it.", str.id) 61 | s.Close() 62 | return nil 63 | } 64 | } else { 65 | debug.Println("Cannot create stream after receiving goaway") 66 | return nil 67 | } 68 | } 69 | 70 | func (s *Session) newServerStream(frame controlFrame) (str *Stream, err error) { 71 | // no stream creation after goaway has been recieved 72 | if !s.goaway_recvd { 73 | str = &Stream{ 74 | id: frame.streamID(), 75 | session: s, 76 | priority: 4, // FIXME need to implement priorities 77 | associated_stream: 0, // FIXME for pushes we need to implement it 78 | control: make(chan controlFrame), 79 | data: make(chan dataFrame), 80 | response: make(chan bool), 81 | eos: make(chan bool), 82 | stop_server: make(chan bool), 83 | flow_req: make(chan int32, 1), 84 | flow_add: make(chan int32, 1), 85 | } 86 | 87 | go str.serve() 88 | 89 | go str.flowManager(INITIAL_FLOW_CONTOL_WINDOW, str.flow_add, str.flow_req) 90 | 91 | // send the SYN_STREAM control frame to get it started 92 | str.control <- frame 93 | return 94 | } else { 95 | return nil, errors.New("Cannot create stream after receiving goaway") 96 | } 97 | 98 | } 99 | 100 | // String returns the Stream ID of the Stream 101 | func (s *Stream) String() string { 102 | return fmt.Sprintf("%d", s.id) 103 | } 104 | 105 | // prepare the header of the request in SPDY format 106 | func (s *Stream) prepareRequestHeader(request *http.Request) (err error) { 107 | url := request.URL 108 | if url != nil && url.Path == "" { 109 | url.Path = "/" 110 | } 111 | if url == nil || url.Scheme == "" || url.Host == "" || url.Path == "" { 112 | err = errors.New(fmt.Sprintf("ERROR: Incomplete path provided: scheme=%s host=%s path=%s", url.Scheme, url.Host, url.Path)) 113 | return 114 | } 115 | path := url.Path 116 | if url.RawQuery != "" { 117 | path += "?" + url.RawQuery 118 | } 119 | if url.Fragment != "" { 120 | path += "#" + url.Fragment 121 | } 122 | 123 | // normalize host:port 124 | if !strings.Contains(url.Host, ":") { 125 | switch url.Scheme { 126 | case "http": 127 | url.Host += ":80" 128 | case "https": 129 | url.Host += ":443" 130 | } 131 | } 132 | 133 | // set all SPDY headers 134 | request.Header.Set(HEADER_METHOD, request.Method) 135 | request.Header.Set(HEADER_PATH, path) 136 | request.Header.Set(HEADER_VERSION, request.Proto) 137 | request.Header.Set(HEADER_HOST, url.Host) 138 | request.Header.Set(HEADER_SCHEME, url.Scheme) 139 | 140 | request.Header.Del("Connection") 141 | request.Header.Del("Host") 142 | request.Header.Del("Keep-Alive") 143 | request.Header.Del("Proxy-Connection") 144 | request.Header.Del("Transfer-Encoding") 145 | 146 | return nil 147 | } 148 | 149 | func (s *Stream) prepareRequestBody(request *http.Request) (body []*dataFrame, err error) { 150 | 151 | body = make([]*dataFrame, 0, 1) 152 | if request.Body == nil { 153 | return body, nil 154 | } 155 | 156 | buf := make([]byte, 32*1024) 157 | n, err := request.Body.Read(buf) 158 | if err != nil && err != io.EOF { 159 | return nil, err 160 | } 161 | total := n 162 | for n > 0 { 163 | data := new(dataFrame) 164 | data.data = make([]byte, n) 165 | copy(data.data, buf[:n]) 166 | body = append(body, data) 167 | n, err = request.Body.Read(buf) 168 | if err != nil && err != io.EOF { 169 | return nil, err 170 | } 171 | total += n 172 | } 173 | 174 | // Half-close the stream. 175 | if len(body) != 0 { 176 | request.Header.Set("Content-Length", fmt.Sprint(total)) 177 | body[len(body)-1].flags = FLAG_FIN 178 | } 179 | request.Body.Close() 180 | 181 | err = nil // in case it was EOF, which is not an error 182 | 183 | return 184 | } 185 | 186 | func (s *Stream) handleRequest(request *http.Request) (err error) { 187 | err = s.prepareRequestHeader(request) 188 | if err != nil { 189 | return 190 | } 191 | 192 | flags := FLAG_NONE 193 | if request.Body == nil { 194 | flags = FLAG_FIN 195 | } 196 | 197 | body, err := s.prepareRequestBody(request) 198 | if err != nil { 199 | return 200 | } 201 | 202 | if len(body) == 0 { 203 | flags = FLAG_FIN 204 | } 205 | 206 | // send the SYN frame to start the stream 207 | f := frameSynStream{session: s.session, stream: s.id, header: request.Header, flags: flags} 208 | debug.Println("Sending SYN_STREAM:", f) 209 | s.session.out <- f 210 | 211 | // send the DATA frames for the body 212 | for _, frame := range body { 213 | frame.stream = s.id 214 | s.session.out <- frame 215 | } 216 | 217 | // need to return now but the data pieces will be picked up 218 | // and the eos channel will be notified when done 219 | 220 | return nil 221 | } 222 | 223 | // Request makes an http request down the client that gets a client Stream 224 | // started and returning the request in the ResponseWriter 225 | func (s *Stream) Request(request *http.Request, writer http.ResponseWriter) (err error) { 226 | 227 | s.response_writer = writer 228 | 229 | err = s.handleRequest(request) 230 | if err != nil { 231 | debug.Println("ERROR in stream.serve/http.Request:", err) 232 | return 233 | } 234 | 235 | debug.Printf("Waiting for #%d to end", s.id) 236 | 237 | // the response is finished sending 238 | <-s.eos 239 | 240 | s.finish_stream() 241 | 242 | return 243 | } 244 | 245 | func (s *Stream) finish_stream() { 246 | 247 | defer no_panics() 248 | deadline := time.After(500 * time.Millisecond) 249 | 250 | select { 251 | case s.stop_server <- true: 252 | // done 253 | case <-deadline: 254 | // well, somehow it was locked 255 | debug.Printf("Stream #%d: Request() timed out while stopping", s.id) 256 | } 257 | 258 | } 259 | 260 | // Takes a SYN_STREAM control frame and kicks off a stream, calling the handler 261 | func (s *Stream) initiate_stream(frame controlFrame) (err error) { 262 | debug.Println("Stream server got SYN_STREAM") 263 | 264 | s.id = frame.streamID() 265 | 266 | data := bytes.NewBuffer(frame.data[4:]) 267 | 268 | // add the stream to the map of streams in the session 269 | // note that above the message sending the ID is zero initially! 270 | s.session.new_stream <- s 271 | 272 | var associated_id uint32 273 | err = binary.Read(data, binary.BigEndian, &associated_id) 274 | if err != nil { 275 | return err 276 | } 277 | s.associated_stream = streamID(associated_id & 0x7fffffff) 278 | 279 | var b uint8 280 | err = binary.Read(data, binary.BigEndian, &b) 281 | if err != nil { 282 | return err 283 | } 284 | s.priority = (b & (0x7 << 5)) >> 5 285 | 286 | // skip over the "slot" of unused space 287 | _, err = io.ReadFull(data, make([]byte, 1)) 288 | if err != nil { 289 | return err 290 | } 291 | 292 | headers := make(http.Header) 293 | // debug.Println("header data:", data.Bytes()) 294 | headers, err = s.session.headerReader.decode(data.Bytes()) 295 | if err != nil { 296 | return err 297 | } 298 | 299 | headers.Del("Connection") 300 | headers.Del("Host") 301 | headers.Del("Keep-Alive") 302 | headers.Del("Proxy-Connection") 303 | headers.Del("Transfer-Encoding") 304 | 305 | s.headers = headers 306 | 307 | // build the frame just for printing it 308 | ss := frameSynStream{ 309 | session: s.session, 310 | stream: s.id, 311 | priority: s.priority, 312 | associated_stream: s.associated_stream, 313 | header: headers, 314 | flags: frame.flags} 315 | 316 | debug.Println("Processing SYN_STREAM", ss) 317 | if frame.isFIN() { 318 | // call the handler 319 | 320 | req := &http.Request{ 321 | Method: headers.Get(HEADER_METHOD), 322 | Proto: headers.Get(HEADER_VERSION), 323 | Header: headers, 324 | RemoteAddr: s.session.conn.RemoteAddr().String(), 325 | } 326 | req.URL, _ = url.ParseRequestURI(headers.Get(HEADER_PATH)) 327 | 328 | // Clear the headers in the session now that the request has them 329 | s.headers = make(http.Header) 330 | 331 | go s.requestHandler(req) 332 | 333 | } else { 334 | var data []byte 335 | 336 | endflag := 0 337 | for endflag == 0 { 338 | deadline := time.After(3 * time.Second) 339 | select { 340 | case cf, ok := <-s.control: 341 | if !ok { 342 | return 343 | } 344 | switch cf.kind { 345 | case FRAME_SYN_STREAM: 346 | err = errors.New("Multiple Syn Streams sent to single Stream") 347 | return 348 | case FRAME_SYN_REPLY: 349 | err = s.handleSynReply(cf) 350 | case FRAME_RST_STREAM: 351 | err = s.handleRstStream(cf) 352 | return 353 | case FRAME_WINDOW_UPDATE: 354 | s.handleWindowUpdate(cf) 355 | default: 356 | panic("TODO: unhandled type of frame received in stream.serve()") 357 | } 358 | case df, ok := <-s.data: 359 | //collecting data 360 | if !ok { 361 | debug.Println("Error collecting data frames", ok) 362 | return 363 | } 364 | data = append(data, df.data...) 365 | if df.isFIN() { 366 | endflag = 1 367 | } 368 | break 369 | 370 | case <-deadline: 371 | //unsuccessfully waited for FIN 372 | // no activity in a while. Assume that body is completely recieved. Bail 373 | //panic("Waited long enough but no data frames recieved") 374 | debug.Println("Waited long enough but no data frames recieved") 375 | endflag = 2 376 | break 377 | } 378 | } 379 | if endflag == 1 { 380 | //http request if data frames collected sucessfully 381 | // call the handler 382 | contLen, _ := strconv.Atoi(headers.Get(HEADER_CONTENT_LENGTH)) 383 | req := &http.Request{ 384 | Method: headers.Get(HEADER_METHOD), 385 | Proto: headers.Get(HEADER_VERSION), 386 | Header: headers, 387 | RemoteAddr: s.session.conn.RemoteAddr().String(), 388 | ContentLength: int64(contLen), 389 | Body: &readCloser{bytes.NewReader(data)}, 390 | } 391 | req.URL, _ = url.ParseRequestURI(headers.Get(HEADER_PATH)) 392 | 393 | // Clear the headers in the session now that the request has them 394 | s.headers = make(http.Header) 395 | 396 | go s.requestHandler(req) 397 | } 398 | 399 | } 400 | 401 | return nil 402 | } 403 | func (s *Stream) requestHandler(req *http.Request) { 404 | // call the handler - this writes the SYN_REPLY and all data frames 405 | s.session.server.Handler.ServeHTTP(s, req) 406 | 407 | debug.Printf("Sending final DATA with FIN the handler for #%d", s.id) 408 | 409 | // send an empty data frame with FIN set to end the deal 410 | frame := dataFrame{stream: s.id, flags: FLAG_FIN} 411 | s.session.out <- frame 412 | 413 | // close shop for this stream's end 414 | if !s.closed { 415 | s.stop_server <- true 416 | } 417 | } 418 | 419 | func (s *Stream) serve() { 420 | 421 | debug.Printf("Stream #%d main loop", s.id) 422 | err := s.stream_loop() 423 | if err != nil { 424 | debug.Println("ERROR in stream loop:", err) 425 | } 426 | s.closed = true 427 | 428 | deadline := time.After(1500 * time.Millisecond) 429 | select { 430 | case s.session.end_stream <- s: 431 | // done, all good! 432 | case <-deadline: 433 | // somehow it was locked 434 | debug.Printf("Stream #%d: timed out and cannot be removed from the session", s.id) 435 | } 436 | 437 | if s.upstream_buffer != nil { 438 | // this is not a server stream 439 | close(s.upstream_buffer) 440 | } 441 | close(s.flow_add) 442 | close(s.flow_req) 443 | debug.Printf("Stream #%d main loop done", s.id) 444 | } 445 | 446 | // stream server loop 447 | func (s *Stream) stream_loop() (err error) { 448 | 449 | for { 450 | deadline := time.After(10 * time.Second) 451 | select { 452 | case cf, ok := <-s.control: 453 | if !ok { 454 | return 455 | } 456 | switch cf.kind { 457 | case FRAME_SYN_STREAM: 458 | err = s.initiate_stream(cf) 459 | debug.Println("Goroutines:", runtime.NumGoroutine()) 460 | case FRAME_SYN_REPLY: 461 | err = s.handleSynReply(cf) 462 | case FRAME_RST_STREAM: 463 | err = s.handleRstStream(cf) 464 | return 465 | case FRAME_WINDOW_UPDATE: 466 | s.handleWindowUpdate(cf) 467 | default: 468 | panic("TODO: unhandled type of frame received in stream.serve()") 469 | } 470 | case df, ok := <-s.data: 471 | if !ok { 472 | return 473 | } 474 | err = s.handleDataFrame(df) 475 | case <-deadline: 476 | // no activity in a while. bail 477 | return 478 | case _, _ = <-s.stop_server: 479 | return 480 | } 481 | if err != nil { 482 | // finish the loop with any error, data or control 483 | return 484 | } 485 | } 486 | } 487 | 488 | // Header makes streams compatible with the net/http handlers interface 489 | func (s *Stream) Header() http.Header { return s.headers } 490 | 491 | // Write makes streams compatible with the net/http handlers interface 492 | func (s *Stream) Write(p []byte) (n int, err error) { 493 | if s.closed { 494 | err = errors.New(fmt.Sprintf("Stream #%d: write on closed stream!", s.id)) 495 | return 496 | } 497 | if !s.wroteHeader { 498 | s.WriteHeader(http.StatusOK) 499 | } 500 | lp := int32(len(p)) 501 | if lp == 0 { 502 | return 503 | } 504 | flow := int32(0) 505 | for lp > flow { 506 | // if there is data to send and it's larger than the flow control window 507 | // we need to stall until we have enough to go 508 | window, ok := <-s.flow_req 509 | debug.Printf("Stream #%d: got %d bytes of flow", s.id, window) 510 | if !ok || s.closed { 511 | debug.Printf("Stream #%d: flow closed!", s.id) 512 | return 0, errors.New(fmt.Sprintf("Stream #%d closed while writing")) 513 | } 514 | flow += window 515 | } 516 | // this is just in case we end up trying to write while on network turbulence 517 | defer no_panics() 518 | for len(p) > 0 { 519 | frame := dataFrame{stream: s.id} 520 | if len(p) < MAX_DATA_PAYLOAD { 521 | frame.data = make([]byte, len(p)) 522 | } else { 523 | frame.data = make([]byte, MAX_DATA_PAYLOAD) 524 | } 525 | copy(frame.data, p) 526 | p = p[len(frame.data):] 527 | s.session.out <- frame 528 | n += len(frame.data) 529 | } 530 | 531 | // put the rest back in the flow control window 532 | s.flow_add <- flow - int32(n) 533 | debug.Printf("Stream #%d: FCW updated -%d: %d -> %d", s.id, int32(n), flow, flow-int32(n)) 534 | 535 | return 536 | } 537 | 538 | // WriteHeader makes streams compatible with the net/http handlers interface 539 | func (s *Stream) WriteHeader(code int) { 540 | if s.wroteHeader { 541 | log.Println("ERROR: Multiple calls to ResponseWriter.WriteHeader.") 542 | return 543 | } 544 | 545 | // send basic SPDY fields 546 | s.headers.Set(HEADER_STATUS, strconv.Itoa(code)+" "+http.StatusText(code)) 547 | s.headers.Set(HEADER_VERSION, "HTTP/1.1") 548 | 549 | if s.headers.Get("Content-Type") == "" { 550 | s.headers.Set("Content-Type", "text/html; charset=utf-8") 551 | } 552 | if s.headers.Get("Date") == "" { 553 | s.headers.Set("Date", time.Now().UTC().Format(http.TimeFormat)) 554 | } 555 | // Write the frame 556 | sr := frameSynReply{session: s.session, stream: s.id, headers: s.headers} 557 | debug.Println("Sending SYN_REPLY", sr) 558 | s.session.out <- sr 559 | s.wroteHeader = true 560 | } 561 | 562 | // takes a SYN_REPLY control frame 563 | func (s *Stream) handleSynReply(frame controlFrame) (err error) { 564 | 565 | debug.Println("Stream server got SYN_REPLY") 566 | 567 | s.headers, err = s.session.headerReader.decode(frame.data[4:]) 568 | if err != nil { 569 | return 570 | } 571 | h := s.response_writer.Header() 572 | for name, values := range s.headers { 573 | if name[0] == ':' { // skip SPDY headers 574 | continue 575 | } 576 | for _, value := range values { 577 | debug.Printf("Header: %s -> %s\n", name, value) 578 | h.Set(name, value) 579 | } 580 | } 581 | status := s.headers.Get(HEADER_STATUS) 582 | code, err := strconv.Atoi(status[0:3]) 583 | if err != nil { 584 | log.Println("ERROR: handleSynReply: got an unparseable status:", status) 585 | } 586 | debug.Printf("Header status code: %d\n", code) 587 | 588 | // *could* this conceivably block or time out? 589 | // we would need to write it through a similar the upstream data sender 590 | // but remember to NOT update the delta window with this data 591 | s.response_writer.WriteHeader(code) 592 | 593 | if frame.isFIN() { 594 | debug.Println("Stream FIN found in SYN_REPLY frame") 595 | s.eos <- true 596 | } 597 | 598 | return nil 599 | } 600 | 601 | // send stream cancellation 602 | func (s *Stream) sendRstStream() { 603 | data := new(bytes.Buffer) 604 | binary.Write(data, binary.BigEndian, s.id) 605 | // FIXME this needs some cleaning 606 | var code uint32 = 5 // CANCEL 607 | binary.Write(data, binary.BigEndian, code) 608 | 609 | rst_stream := controlFrame{kind: FRAME_RST_STREAM, data: data.Bytes()} 610 | s.session.out <- rst_stream 611 | } 612 | 613 | // takes a DATA frame and adds it to the running body of the stream 614 | func (s *Stream) handleDataFrame(frame dataFrame) (err error) { 615 | 616 | debug.Println("Stream server got DATA") 617 | 618 | if len(s.upstream_buffer) >= NORTHBOUND_SLOTS { 619 | msg := fmt.Sprintf("upstream buffering hit the limit of %d buffers", NORTHBOUND_SLOTS) 620 | log.Println(msg) 621 | err = errors.New(msg) 622 | return 623 | } 624 | 625 | debug.Printf("Stream #%d adding +%d to upstream data queue. FIN? %v", s.id, len(frame.data), frame.isFIN()) 626 | s.upstream_buffer <- upstream_data{frame.data, frame.isFIN()} 627 | debug.Printf("Stream #%d data queue size: %d", s.id, len(s.upstream_buffer)) 628 | 629 | return 630 | } 631 | 632 | func (s *Stream) northboundBufferSender() { 633 | defer no_panics() 634 | for f := range s.upstream_buffer { 635 | var err error 636 | data := f.data 637 | size := len(data) 638 | for l := size; l > 0; l = len(data) { 639 | debug.Printf("Stream #%d trying to write %d upstream bytes", s.id, l) 640 | written, err := s.response_writer.Write(data) 641 | if err == nil && written == l { 642 | // sunny day scenario! 643 | break 644 | } 645 | if err != nil { 646 | if !isBrokenPipe(err) { 647 | log.Printf("ERROR found writing northbound stream #%d:, %#v", s.id, err) 648 | } 649 | s.sendRstStream() 650 | s.eos <- true 651 | return 652 | } 653 | if written != l { 654 | debug.Printf("Stream #%d: northboundBufferSender: only %d of %d were written", s.id, written, l) 655 | time.Sleep(2 * time.Second) 656 | } 657 | data = data[written:] 658 | } 659 | // all good with this write 660 | if size > 0 { 661 | debug.Printf("Stream #%d: %d bytes successfully written upstream", s.id, size) 662 | wupdate := windowUpdateFor(s.id, size) 663 | s.session.out <- wupdate 664 | s.session.out <- windowUpdateFor(0, size) // update session window 665 | } 666 | if err == nil && f.final { 667 | debug.Printf("Stream #%d: last upstream data done!", s.id) 668 | s.eos <- true 669 | break 670 | } 671 | } 672 | debug.Printf("Stream #%d: northboundBufferSender done!", s.id) 673 | } 674 | 675 | // Close does nothing and is here only to allow the data of a request to become 676 | // the body of a response 677 | func (r *readCloser) Close() error { return nil } 678 | 679 | func (s *Stream) handleRstStream(frame controlFrame) (err error) { 680 | 681 | debug.Println("Stream server got RST_STREAM") 682 | 683 | id := frame.streamID() 684 | 685 | data := bytes.NewBuffer(frame.data[4:]) 686 | var status uint32 687 | err = binary.Read(data, binary.BigEndian, &status) 688 | if err != nil { 689 | return err 690 | } 691 | debug.Printf("Stream #%d cancelled with status code %d", id, status) 692 | 693 | return nil 694 | } 695 | 696 | // handle WINDOW_UPDATE from the other side 697 | func (s *Stream) handleWindowUpdate(frame controlFrame) { 698 | 699 | debug.Println("Stream server got WINDOW_UPDATE") 700 | 701 | if s.closed { 702 | return 703 | } 704 | 705 | data := bytes.NewBuffer(frame.data[4:8]) 706 | var size uint32 707 | binary.Read(data, binary.BigEndian, &size) 708 | size &= 0x7fffffff 709 | 710 | // add the window size update from the flow control window 711 | s.flow_add <- int32(size) 712 | debug.Printf("Stream #%d window size +%d", s.id, int32(size)) 713 | } 714 | 715 | // flowManager is a coroutine to manage the flow control window in an atomic manner 716 | // so that there are no race conditions and it's easier to expand later w/ SETTINGS 717 | func (s *Stream) flowManager(initial int32, in <-chan int32, out chan<- int32) { 718 | debug.Printf("Stream #%d flow manager started", s.id) 719 | // no panics; it could be that we get clipped trying to send when out is closed 720 | defer no_panics() 721 | sfcw := initial 722 | for { 723 | if sfcw > 0 { 724 | debug.Printf("Stream #%d window size %d", s.id, sfcw) 725 | select { 726 | case v, ok := <-in: 727 | if s.closed || !ok { 728 | return 729 | } 730 | sfcw += v 731 | case out <- sfcw: 732 | sfcw = 0 733 | } 734 | } else { 735 | debug.Printf("Stream #%d window size %d", s.id, sfcw) 736 | v, ok := <-in 737 | if s.closed || !ok { 738 | return 739 | } 740 | sfcw += v 741 | } 742 | if s.closed { 743 | return 744 | } 745 | } 746 | debug.Printf("Stream #%d flow manager done", s.id) 747 | } 748 | --------------------------------------------------------------------------------