Skip to content
Browse files

Support connection hijacking

When the connection is hijacked, we log the information (as it is
known to us) and relinquish control of the connection. We no longer
track this request after hijacking.
  • Loading branch information...
1 parent 436fbcf commit bd03c773db22297a309ba6c24b872f51466c6a8d @cespare committed Sep 12, 2013
Showing with 48 additions and 25 deletions.
  1. +48 −25 apachelog.go
View
73 apachelog.go
@@ -22,6 +22,8 @@ Example:
package apachelog
import (
+ "bufio"
+ "errors"
"fmt"
"io"
"net"
@@ -31,24 +33,43 @@ import (
// Using a variant of apache common log format used in Ruby's Rack::CommonLogger which includes response time
// in seconds at the the end of the log line.
-const apacheFormatPattern = "%s - - [%s] \"%s %s %s\" %d %d %0.4f\n"
+const apacheFormatPattern = "%s - - [%s] \"%s %s %s\" %d %d %.4f\n"
+
+var ErrHijackingNotSupported = errors.New("hijacking is not supported")
// record is a wrapper around a ResponseWriter that carries other metadata needed to write a log line.
type record struct {
http.ResponseWriter
+ out io.Writer // Same as the handler's out; the record needs to be able to log itself.
+
+ // Only used for intermediate calculations
+ startTime time.Time
+ // Fields needed to produce log line
ip string
- time time.Time
+ endTime time.Time
method, uri, protocol string
status int
responseBytes int64
elapsedTime time.Duration
}
-// Log writes the record out as a single log line to out.
-func (r *record) Log(out io.Writer) {
- timeFormatted := r.time.Format("02/Jan/2006 15:04:05")
- fmt.Fprintf(out, apacheFormatPattern, r.ip, timeFormatted, r.method, r.uri, r.protocol, r.status,
+// start sets up any initial state for this record before it is used to serve a request.
+func (r *record) start() {
+ r.startTime = time.Now()
+}
+
+// finish finalizes any data and logs the request.
+func (r *record) finish() {
+ r.endTime = time.Now()
+ r.elapsedTime = r.endTime.Sub(r.startTime)
+ r.log()
+}
+
+// log writes the record out as a single log line to r.out.
+func (r *record) log() {
+ timeFormatted := r.endTime.Format("02/Jan/2006 15:04:05")
+ fmt.Fprintf(r.out, apacheFormatPattern, r.ip, timeFormatted, r.method, r.uri, r.protocol, r.status,
r.responseBytes, r.elapsedTime.Seconds())
}
@@ -65,6 +86,15 @@ func (r *record) WriteHeader(status int) {
r.ResponseWriter.WriteHeader(status)
}
+func (r *record) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ w, ok := r.ResponseWriter.(http.Hijacker)
+ if !ok {
+ return nil, nil, ErrHijackingNotSupported
+ }
+ r.finish()
+ return w.Hijack()
+}
+
// handler is an http.Handler that logs each response.
type handler struct {
http.Handler
@@ -82,25 +112,18 @@ func NewHandler(h http.Handler, out io.Writer) http.Handler {
// ServeHTTP delegates to the underlying handler's ServeHTTP method and writes one log line for every call.
func (h *handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
- record := &record{
- ResponseWriter: rw,
- ip: getIP(r.RemoteAddr),
- time: time.Time{},
- method: r.Method,
- uri: r.RequestURI,
- protocol: r.Proto,
- status: http.StatusOK,
- elapsedTime: time.Duration(0),
- }
-
- startTime := time.Now()
- h.Handler.ServeHTTP(record, r)
- finishTime := time.Now()
-
- record.time = finishTime
- record.elapsedTime = finishTime.Sub(startTime)
-
- record.Log(h.out)
+ rec := new(record)
+ rec.start()
+ rec.ResponseWriter = rw
+ rec.out = h.out
+ rec.ip = getIP(r.RemoteAddr)
+ rec.method = r.Method
+ rec.uri = r.RequestURI
+ rec.protocol = r.Proto
+ rec.status = http.StatusOK
+
+ h.Handler.ServeHTTP(rec, r)
+ rec.finish()
}
// getIP makes a best-effort attempt at getting the IP from http.Request.RemoteAddr. For a Go server, they

0 comments on commit bd03c77

Please sign in to comment.
Something went wrong with that request. Please try again.