From bf3a40ffb7d68ed5e244da466f2af6763c06a88f Mon Sep 17 00:00:00 2001 From: Darshil Chanpura Date: Thu, 5 Jul 2018 23:48:35 +0530 Subject: [PATCH] Updated README, added code for logging handler and its record --- README.md | 18 +++++++++++++++ log_handler.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++ log_record.go | 52 ++++++++++++++++++++++++++++++++++++++++++ main.go | 44 ++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 log_handler.go create mode 100644 log_record.go create mode 100644 main.go diff --git a/README.md b/README.md index e69de29..262e835 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,18 @@ +# dumb-http +## Simple HTTP Server + +## Usage + +``` +dumb-http [-path path-to-serve] [port] +``` +### Example +``` +$ dumb-http -path ./docs +Serving at http://0.0.0.0:8000/ from ./docs +127.0.0.1 - - [05/Jul/2018 18:08:16] "GET / HTTP/1.1" 200 38 0.0000 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:61.0) Gecko/20100101 Firefox/61.0" +``` + +>Inspired by Python module http.server + +> Reference: https://gist.github.com/cespare/3985516 diff --git a/log_handler.go b/log_handler.go new file mode 100644 index 0000000..7757c1f --- /dev/null +++ b/log_handler.go @@ -0,0 +1,61 @@ +package main + +import ( + "io" + "net/http" + "strings" + "time" +) + +// LoggingHandler is to be used as wrapper of mux. +type LoggingHandler struct { + handler http.Handler + out io.Writer +} + +// NewLoggingHandler for creating a new Logging Handler +func NewLoggingHandler(handler http.Handler, out io.Writer) http.Handler { + return &LoggingHandler{ + handler: handler, + out: out, + } +} + +func (h LoggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + ipPort := strings.Split(r.RemoteAddr, ":") + + logRecord := &LogRecord{ + ResponseWriter: w, + time: time.Time{}, + method: r.Method, + statusCode: http.StatusOK, + protocol: r.Proto, + path: r.RequestURI, + clientIP: strings.Join(ipPort[:len(ipPort)-1], ":"), + referer: "-", + userAgent: "-", + totalTime: time.Duration(0), + } + + // For logging Real IP Address + if realIP := r.Header.Get("X-Real-IP"); realIP != "" { + logRecord.clientIP = realIP + } + + // For logging Referer URL + if referer := r.Header.Get("Referer"); referer != "" { + logRecord.referer = referer + } + + // For logging User Agent + if userAgent := r.Header.Get("User-Agent"); userAgent != "" { + logRecord.userAgent = userAgent + } + startTime := time.Now() + h.handler.ServeHTTP(logRecord, r) + endTime := time.Now() + logRecord.time = endTime.UTC() + logRecord.totalTime = endTime.Sub(startTime) / 1000.0 + logRecord.Log(h.out) +} diff --git a/log_record.go b/log_record.go new file mode 100644 index 0000000..53a050b --- /dev/null +++ b/log_record.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "io" + "net/http" + "time" +) + +const ( + // LogPattern is format for writing the logRecord + LogPattern = "%s - - [%s] \"%s\" %d %d %.4f \"%s\" \"%s\"\n" + // DateTimeFormat is for logging Date and Time of request + DateTimeFormat = "02/Jan/2006 15:04:05" // Minimal + // DateTimeFormat = "Mon, 02 Jan 2006 15:04:05 MST" // Full +) + +// LogRecord struct extends http.ResponseWriter interface which has different methods included in it. +type LogRecord struct { + http.ResponseWriter + method string + path string + clientIP string + time time.Time + contentLength int64 + protocol string + statusCode int + referer string + userAgent string + totalTime time.Duration +} + +// Log method to be called for logging to "out" +func (l *LogRecord) Log(out io.Writer) { + timeFormatted := l.time.Format(DateTimeFormat) + requestLine := l.method + " " + l.path + " " + l.protocol + fmt.Fprintf(out, LogPattern, l.clientIP, timeFormatted, requestLine, + l.statusCode, l.contentLength, l.totalTime.Seconds(), l.referer, l.userAgent) +} + +// WriteHeader method has been extended to record status code from previous handler. +func (l *LogRecord) WriteHeader(statusCode int) { + l.statusCode = statusCode + l.ResponseWriter.WriteHeader(statusCode) +} + +// Write method has been extended to record the bytes written or the content length +func (l *LogRecord) Write(p []byte) (int, error) { + written, err := l.ResponseWriter.Write(p) + l.contentLength += int64(written) + return written, err +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ab58903 --- /dev/null +++ b/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" +) + +var ( + localDir = "." + host = "0.0.0.0" + port = "8000" +) + +func init() { + flag.StringVar(&localDir, "path", ".", "Path to serve from") +} + +func main() { + flag.Parse() + // Check for port in commandline + if commandPort := flag.Arg(0); commandPort != "" { + port = commandPort + } + // Start serving... + serve(host + ":" + port) +} + +func serve(addr string) { + mux := http.NewServeMux() + mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(localDir)))) + server := http.Server{ + Addr: addr, + Handler: NewLoggingHandler(mux, os.Stdout), + } + fmt.Printf("Serving at http://%s/ from %s\n", addr, localDir) + err := server.ListenAndServe() + if err != nil { + log.Fatal(err) + } + +}