github.com/orofarne/hammy@v0.0.0-20130409105742-374fadfd6ecb/src/hammy/http.go (about) 1 package hammy 2 3 import ( 4 "fmt" 5 "log" 6 "time" 7 "runtime" 8 "net/http" 9 "encoding/json" 10 "github.com/ugorji/go-msgpack" 11 ) 12 13 // Http server object 14 // InitMetric must be called before use 15 type HttpServer struct{ 16 // Request handler object 17 RHandler RequestHandler 18 // Metrics 19 ms *MetricSet 20 mReqTimer *TimerMetric 21 mReceivedValues *CounterMetric 22 mCounter200, mCounter400, mCounter500 *CounterMetric 23 } 24 25 // Initialize metric objects 26 func (h *HttpServer) InitMetrics(metricsNamespace string) { 27 h.ms = NewMetricSet(metricsNamespace, 30*time.Second) 28 h.mReqTimer = h.ms.NewTimer("requests") 29 h.mReceivedValues = h.ms.NewCounter("received_values") 30 h.mCounter200 = h.ms.NewCounter("2xx") 31 h.mCounter400 = h.ms.NewCounter("4xx") 32 h.mCounter500 = h.ms.NewCounter("5xx") 33 } 34 35 func (h *HttpServer) reqStatistics(req *IncomingMessage) { 36 var values_received uint64 37 38 for _, hD := range req.Data { 39 for _, v := range hD { 40 values_received += uint64(len(v)) 41 } 42 } 43 44 h.mReceivedValues.Add(values_received) 45 } 46 47 // Request handler 48 func (h *HttpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 49 // statistics 50 τ := h.mReqTimer.NewObservation() 51 defer func() { τ.End() } () 52 53 if r.Method != "POST" { 54 http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 55 h.mCounter400.Add(1) 56 return 57 } 58 59 defer func() { runtime.GC() } () 60 61 contentTypeHeader, headerFound := r.Header["Content-Type"] 62 var contentType string 63 if headerFound && len(contentTypeHeader) > 0 { 64 contentType = contentTypeHeader[0] 65 } else { 66 contentType = "application/json" 67 } 68 69 type DataDecoder interface{ 70 Decode(interface{}) error 71 } 72 73 type DataEncoder interface{ 74 Encode(interface{}) error 75 } 76 77 var dataDecoder DataDecoder 78 var dataEncoder DataEncoder 79 switch contentType { 80 case "application/json": 81 dataDecoder = json.NewDecoder(r.Body) 82 dataEncoder = json.NewEncoder(w) 83 case "application/x-msgpack": 84 dataDecoder = msgpack.NewDecoder(r.Body, nil) 85 dataEncoder = msgpack.NewEncoder(w) 86 default: 87 http.Error(w, "Bad Request", http.StatusBadRequest) 88 fmt.Fprintf(w, "Unsupported Content-Type\n") 89 h.mCounter400.Add(1) 90 return 91 } 92 93 var req IncomingMessage 94 err := dataDecoder.Decode(&req) 95 if err != nil { 96 http.Error(w, "Bad Request", http.StatusBadRequest) 97 fmt.Fprintf(w, "%v\n", err); 98 h.mCounter400.Add(1) 99 return 100 } 101 102 h.reqStatistics(&req) // Statistics 103 104 errs := h.RHandler.Handle(req.Data) 105 errs_str := make(map[string]string) 106 for k, e := range errs { 107 errs_str[k] = e.Error() 108 } 109 110 resp := ResponseMessage{ 111 Errors: errs_str, 112 } 113 114 w.Header().Set("Content-Type", contentType) 115 err = dataEncoder.Encode(&resp) 116 117 if err != nil { 118 http.Error(w, "Internal Server Error", http.StatusInternalServerError) 119 fmt.Fprintf(w, "%v\n", err); 120 log.Printf("Internal Server Error: %v", err) 121 h.mCounter500.Add(1) 122 return 123 } 124 125 h.mCounter200.Add(1) 126 } 127 128 // Start http interface and lock goroutine untill fatal error 129 func StartHttp(rh RequestHandler, cfg Config, metricsNamespace string) error { 130 h := &HttpServer{ 131 RHandler: rh, 132 } 133 134 h.InitMetrics(metricsNamespace) 135 136 // Setup server 137 s := &http.Server{ 138 Addr: cfg.IncomingHttp.Addr, 139 Handler: h, 140 ReadTimeout: 30 * time.Second, 141 WriteTimeout: 30 * time.Second, 142 MaxHeaderBytes: 1 << 20, 143 } 144 145 return s.ListenAndServe() 146 }