fixing up dockerfile for build and added missing vendor

pull/3/head
josebarn 8 years ago
parent 3fe7bdb762
commit e2ca326b66

@ -2,12 +2,17 @@
FROM golang:latest
# Copy in code...
RUN mkdir /app
ADD main.go /app/
RUN mkdir -p /go/src/github.com/josebarn/drone-with-go
ADD main.go /go/src/github.com/josebarn/drone-with-go/
ADD api/ /go/src/github.com/josebarn/drone-with-go/api/
ADD model/ /go/src/github.com/josebarn/drone-with-go/model/
ADD vendor/ /go/src/github.com/josebarn/drone-with-go/vendor/
RUN ls -laR
# Build app
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello .
WORKDIR /go/src/github.com/josebarn/drone-with-go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o hello-world .
# Could use something even smaller...
FROM alpine:latest
@ -15,9 +20,9 @@ RUN apk --no-cache add ca-certificates
RUN mkdir /app
WORKDIR /app/
COPY --from=0 /app/hello .
COPY --from=0 /go/src/github.com/josebarn/drone-with-go/hello-world .
# Expose ports for future REST Interface
EXPOSE 8080
CMD ["/app/hello"]
CMD ["/app/hello-world"]

@ -0,0 +1,20 @@
Copyright (c) 2016-Present https://github.com/go-chi authors
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

@ -0,0 +1,24 @@
# render
The `render` package helps manage HTTP request / response payloads.
Every well-designed, robust and maintainable Web Service / REST API also needs
well-*defined* request and response payloads. Together with the endpoint handlers,
the request and response payloads make up the contract between your server and the
clients calling on it.
Typically in a REST API application, you will have your data models (objects/structs)
that hold lower-level runtime application state, and at times you need to assemble,
decorate, hide or transform the representation before responding to a client. That
server output (response payload) structure, is also likely the input structure to
another handler on the server.
This is where `render` comes in - offering a few simple helpers and interfaces to
provide a simple pattern for managing payload encoding and decoding.
We've also combined it with some helpers for responding to content types and parsing
request bodies. Please have a look at the [rest](https://github.com/go-chi/chi/blob/master/_examples/rest/main.go)
example which uses the latest chi/render sub-pkg.
All feedback is welcome, thank you!

@ -0,0 +1,84 @@
package render
import (
"context"
"net/http"
"strings"
)
var (
ContentTypeCtxKey = &contextKey{"ContentType"}
)
// ContentType is an enumeration of common HTTP content types.
type ContentType int
// ContentTypes handled by this package.
const (
ContentTypeUnknown = iota
ContentTypePlainText
ContentTypeHTML
ContentTypeJSON
ContentTypeXML
ContentTypeForm
ContentTypeEventStream
)
func GetContentType(s string) ContentType {
s = strings.TrimSpace(strings.Split(s, ";")[0])
switch s {
case "text/plain":
return ContentTypePlainText
case "text/html", "application/xhtml+xml":
return ContentTypeHTML
case "application/json", "text/javascript":
return ContentTypeJSON
case "text/xml", "application/xml":
return ContentTypeXML
case "application/x-www-form-urlencoded":
return ContentTypeForm
case "text/event-stream":
return ContentTypeEventStream
default:
return ContentTypeUnknown
}
}
// SetContentType is a middleware that forces response Content-Type.
func SetContentType(contentType ContentType) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), ContentTypeCtxKey, contentType))
next.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
}
// GetRequestContentType is a helper function that returns ContentType based on
// context or request headers.
func GetRequestContentType(r *http.Request) ContentType {
if contentType, ok := r.Context().Value(ContentTypeCtxKey).(ContentType); ok {
return contentType
}
return GetContentType(r.Header.Get("Content-Type"))
}
func GetAcceptedContentType(r *http.Request) ContentType {
if contentType, ok := r.Context().Value(ContentTypeCtxKey).(ContentType); ok {
return contentType
}
var contentType ContentType
// Parse request Accept header.
fields := strings.Split(r.Header.Get("Accept"), ",")
if len(fields) > 0 {
contentType = GetContentType(strings.TrimSpace(fields[0]))
}
if contentType == ContentTypeUnknown {
contentType = ContentTypePlainText
}
return contentType
}

@ -0,0 +1,44 @@
package render
import (
"encoding/json"
"encoding/xml"
"errors"
"io"
"io/ioutil"
"net/http"
)
// Decode is a package-level variable set to our default Decoder. We do this
// because it allows you to set render.Decode to another function with the
// same function signature, while also utilizing the render.Decoder() function
// itself. Effectively, allowing you to easily add your own logic to the package
// defaults. For example, maybe you want to impose a limit on the number of
// bytes allowed to be read from the request body.
var Decode = DefaultDecoder
func DefaultDecoder(r *http.Request, v interface{}) error {
var err error
switch GetRequestContentType(r) {
case ContentTypeJSON:
err = DecodeJSON(r.Body, v)
case ContentTypeXML:
err = DecodeXML(r.Body, v)
// case ContentTypeForm: // TODO
default:
err = errors.New("render: unable to automatically decode the request content type")
}
return err
}
func DecodeJSON(r io.Reader, v interface{}) error {
defer io.Copy(ioutil.Discard, r)
return json.NewDecoder(r).Decode(v)
}
func DecodeXML(r io.Reader, v interface{}) error {
defer io.Copy(ioutil.Discard, r)
return xml.NewDecoder(r).Decode(v)
}

@ -0,0 +1,134 @@
package render
import (
"net/http"
"reflect"
)
// Renderer interface for managing response payloads.
type Renderer interface {
Render(w http.ResponseWriter, r *http.Request) error
}
// Binder interface for managing request payloads.
type Binder interface {
Bind(r *http.Request) error
}
// Bind decodes a request body and executes the Binder method of the
// payload structure.
func Bind(r *http.Request, v Binder) error {
if err := Decode(r, v); err != nil {
return err
}
return binder(r, v)
}
// Render renders a single payload and respond to the client request.
func Render(w http.ResponseWriter, r *http.Request, v Renderer) error {
if err := renderer(w, r, v); err != nil {
return err
}
Respond(w, r, v)
return nil
}
// RenderList renders a slice of payloads and responds to the client request.
func RenderList(w http.ResponseWriter, r *http.Request, l []Renderer) error {
for _, v := range l {
if err := renderer(w, r, v); err != nil {
return err
}
}
Respond(w, r, l)
return nil
}
// Executed top-down
func renderer(w http.ResponseWriter, r *http.Request, v Renderer) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
// We call it top-down.
if err := v.Render(w, r); err != nil {
return err
}
// We're done if the Renderer isn't a struct object
if rv.Kind() != reflect.Struct {
return nil
}
// For structs, we call Render on each field that implements Renderer
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if f.Type().Implements(rendererType) {
if f.IsNil() {
continue
}
fv := f.Interface().(Renderer)
if err := renderer(w, r, fv); err != nil {
return err
}
}
}
return nil
}
// Executed bottom-up
func binder(r *http.Request, v Binder) error {
rv := reflect.ValueOf(v)
if rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
// Call Binder on non-struct types right away
if rv.Kind() != reflect.Struct {
return v.Bind(r)
}
// For structs, we call Bind on each field that implements Binder
for i := 0; i < rv.NumField(); i++ {
f := rv.Field(i)
if f.Type().Implements(binderType) {
if f.IsNil() {
continue
}
fv := f.Interface().(Binder)
if err := binder(r, fv); err != nil {
return err
}
}
}
// We call it bottom-up
if err := v.Bind(r); err != nil {
return err
}
return nil
}
var (
rendererType = reflect.TypeOf(new(Renderer)).Elem()
binderType = reflect.TypeOf(new(Binder)).Elem()
)
// contextKey is a value for use with context.WithValue. It's used as
// a pointer so it fits in an interface{} without allocation. This technique
// for defining context keys was copied from Go 1.7's new use of context in net/http.
type contextKey struct {
name string
}
func (k *contextKey) String() string {
return "chi render context value " + k.name
}

@ -0,0 +1,228 @@
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 <?xml header in first 100 bytes (just in case there're some XML comments).
findHeaderUntil := len(b)
if findHeaderUntil > 100 {
findHeaderUntil = 100
}
if !bytes.Contains(b[:findHeaderUntil], []byte("<?xml")) {
// No header found. Print it out first.
w.Write([]byte(xml.Header))
}
w.Write(b)
}
// NoContent returns a HTTP 204 "No Content" response.
func NoContent(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(204)
}
func channelEventStream(w http.ResponseWriter, r *http.Request, v interface{}) {
if reflect.TypeOf(v).Kind() != reflect.Chan {
panic(fmt.Sprintf("render: event stream expects a channel, not %v", reflect.TypeOf(v).Kind()))
}
w.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.WriteHeader(200)
ctx := r.Context()
for {
switch chosen, recv, ok := reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ctx.Done())},
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(v)},
}); chosen {
case 0: // equivalent to: case <-ctx.Done()
w.Write([]byte("event: error\ndata: {\"error\":\"Server Timeout\"}\n\n"))
return
default: // equivalent to: case v, ok := <-stream
if !ok {
w.Write([]byte("event: EOF\n\n"))
return
}
v := recv.Interface()
// Build each channel item.
if rv, ok := v.(Renderer); ok {
err := renderer(w, r, rv)
if err != nil {
v = err
} else {
v = rv
}
}
bytes, err := json.Marshal(v)
if err != nil {
w.Write([]byte(fmt.Sprintf("event: error\ndata: {\"error\":\"%v\"}\n\n", err)))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
continue
}
w.Write([]byte(fmt.Sprintf("event: data\ndata: %s\n\n", bytes)))
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}
}
// channelIntoSlice buffers channel data into a slice.
func channelIntoSlice(w http.ResponseWriter, r *http.Request, from interface{}) interface{} {
ctx := r.Context()
var to []interface{}
for {
switch chosen, recv, ok := reflect.Select([]reflect.SelectCase{
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(ctx.Done())},
{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(from)},
}); chosen {
case 0: // equivalent to: case <-ctx.Done()
http.Error(w, "Server Timeout", 504)
return nil
default: // equivalent to: case v, ok := <-stream
if !ok {
return to
}
v := recv.Interface()
// Render each channel item.
if rv, ok := v.(Renderer); ok {
err := renderer(w, r, rv)
if err != nil {
v = err
} else {
v = rv
}
}
to = append(to, v)
}
}
}

@ -14,6 +14,12 @@
"revision": "4c5a584b324b74af3e9cfeaf6265d14ae2fdfc99",
"revisionTime": "2017-07-12T12:12:00Z"
},
{
"checksumSHA1": "KlhjFlnkWjrgYdOiUCgD+uuiCRU=",
"path": "github.com/go-chi/render",
"revision": "9f855fadd4b8cde7773f9ef51f6b2705af239519",
"revisionTime": "2017-07-06T22:11:03Z"
},
{
"checksumSHA1": "9LeR7BH4PSu8LRDZ8bY7QY1HXJE=",
"path": "github.com/urfave/cli",

Loading…
Cancel
Save