package render import ( "bytes" "context" "encoding/json" "encoding/xml" "fmt" "net/http" "reflect" ) // M is a convenience alias for quickly building a map structure that is going // out to a responder. Just a short-hand. type M map[string]interface{} // Respond is a package-level variable set to our default Responder. We do this // because it allows you to set render.Respond to another function with the // same function signature, while also utilizing the render.Responder() function // itself. Effectively, allowing you to easily add your own logic to the package // defaults. For example, maybe you want to test if v is an error and respond // differently, or log something before you respond. var Respond = DefaultResponder // StatusCtxKey is a context key to record a future HTTP response status code. var StatusCtxKey = &contextKey{"Status"} // Status sets a HTTP response status code hint into request context at any point // during the request life-cycle. Before the Responder sends its response header // it will check the StatusCtxKey func Status(r *http.Request, status int) { *r = *r.WithContext(context.WithValue(r.Context(), StatusCtxKey, status)) } // Respond handles streaming JSON and XML responses, automatically setting the // Content-Type based on request headers. It will default to a JSON response. func DefaultResponder(w http.ResponseWriter, r *http.Request, v interface{}) { if v != nil { switch reflect.TypeOf(v).Kind() { case reflect.Chan: switch GetAcceptedContentType(r) { case ContentTypeEventStream: channelEventStream(w, r, v) return default: v = channelIntoSlice(w, r, v) } } } // Format response based on request Accept header. switch GetAcceptedContentType(r) { case ContentTypeJSON: JSON(w, r, v) case ContentTypeXML: XML(w, r, v) default: JSON(w, r, v) } } // PlainText writes a string to the response, setting the Content-Type as // text/plain. func PlainText(w http.ResponseWriter, r *http.Request, v string) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") if status, ok := r.Context().Value(StatusCtxKey).(int); ok { w.WriteHeader(status) } w.Write([]byte(v)) } // Data writes raw bytes to the response, setting the Content-Type as // application/octet-stream. func Data(w http.ResponseWriter, r *http.Request, v []byte) { w.Header().Set("Content-Type", "application/octet-stream") if status, ok := r.Context().Value(StatusCtxKey).(int); ok { w.WriteHeader(status) } w.Write(v) } // HTML writes a string to the response, setting the Content-Type as text/html. func HTML(w http.ResponseWriter, r *http.Request, v string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") if status, ok := r.Context().Value(StatusCtxKey).(int); ok { w.WriteHeader(status) } w.Write([]byte(v)) } // JSON marshals 'v' to JSON, automatically escaping HTML and setting the // Content-Type as application/json. func JSON(w http.ResponseWriter, r *http.Request, v interface{}) { buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(true) if err := enc.Encode(v); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") if status, ok := r.Context().Value(StatusCtxKey).(int); ok { w.WriteHeader(status) } w.Write(buf.Bytes()) } // XML marshals 'v' to JSON, setting the Content-Type as application/xml. It // will automatically prepend a generic XML header (see encoding/xml.Header) if // one is not found in the first 100 bytes of 'v'. func XML(w http.ResponseWriter, r *http.Request, v interface{}) { b, err := xml.Marshal(v) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/xml; charset=utf-8") if status, ok := r.Context().Value(StatusCtxKey).(int); ok { w.WriteHeader(status) } // Try to find 100 { findHeaderUntil = 100 } if !bytes.Contains(b[:findHeaderUntil], []byte("