// Package wshttp contains a basic implementation of the interfaces
// 'http.ResponseWriter' and 'http.Hijacker' centered on websocket usage,
// specially the handshake response.
package wshttp
import (
"bufio"
"bytes"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
)
// Response implements 'http.ResponseWriter' and 'http.Hijacker'.
type Response struct {
Res http.Response
Con net.Conn
hasStatus bool
hijacked bool
}
// Header implements 'http.ResponseWriter' strictly for websocket connections.
func (r *Response) Header() http.Header {
if r == nil {
return nil
}
if r.Res.Header == nil {
r.Res.Header = make(map[string][]string)
}
return r.Res.Header
}
// WriteHeader implements 'http.ResponseWriter' strictly for websocket
// connections.
func (r *Response) WriteHeader(code int) {
if r == nil {
return
}
if r.hasStatus {
return
}
r.hasStatus = true
r.Res.StatusCode = code
r.Res.Status = http.StatusText(code)
}
// Write implements 'http.ResponseWriter' strictly for websocket connections.
func (r *Response) Write(data []byte) (int, error) {
if r == nil {
return 0, fmt.Errorf("nil response")
}
if r.hijacked {
return 0, http.ErrHijacked
}
if !r.hasStatus {
r.Res.StatusCode = http.StatusOK
r.Res.Status = http.StatusText(http.StatusOK)
}
if r.Res.Body == nil {
r.Res.Body = io.NopCloser(bytes.NewBuffer(data))
}
var dump, err = httputil.DumpResponse(&r.Res, true)
if err != nil {
return 0, err
}
return r.Con.Write(dump)
}
// Hijack implements 'http.Hijacker'. The response body is drained, as it's not
// used in websocket handshakes.
func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error) {
if r == nil {
return nil, nil, fmt.Errorf("nil response")
}
if r.hijacked {
return nil, nil, http.ErrHijacked
}
var err error
r.hijacked = true
if r.Res.Body != nil {
if _, err = io.Copy(io.Discard, r.Res.Body); err != nil {
return nil, nil, fmt.Errorf("unable to drain response body: %v", err)
}
}
return r.Con, bufio.NewReadWriter(bufio.NewReader(r.Con), bufio.NewWriter(r.Con)), nil
}
// NewResponse instantiates an HTTP response for websocket handshake requests.
func NewResponse(con net.Conn) *Response {
return &Response{
Con: con,
Res: http.Response{Header: make(map[string][]string)},
}
}