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  }