forked from openshift/geard
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.go
158 lines (135 loc) · 4.09 KB
/
server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Serve jobs over the http protocol, and provide a marshalling interface
// for the core geard jobs.
package http
import (
"fmt"
"io"
"log"
"mime"
"net/http"
"reflect"
"strings"
"github.com/openshift/geard/config"
"github.com/openshift/geard/dispatcher"
"github.com/openshift/geard/http/client"
"github.com/openshift/geard/jobs"
"github.com/openshift/go-json-rest"
)
type HttpConfiguration struct {
Docker config.DockerConfiguration
Dispatcher *dispatcher.Dispatcher
}
type HttpContext struct {
jobs.JobContext
ApiVersion string
}
type JobHandler func(*HttpConfiguration, *HttpContext, *rest.Request) (interface{}, error)
type ExtensionMap map[client.RemoteJob]JobHandler
func (conf *HttpConfiguration) Handler() (http.Handler, error) {
handler := rest.ResourceHandler{
EnableRelaxedContentType: true,
EnableResponseStackTrace: true,
EnableGzip: false,
}
handlers := make(ExtensionMap)
for _, ext := range extensions {
routes := ext.Routes()
for key := range routes {
handlers[key] = routes[key]
}
}
routes := make([]rest.Route, 0, len(handlers))
for key := range handlers {
routes = append(routes, rest.Route{
key.HttpMethod(),
key.HttpPath(),
conf.handleWithMethod(handlers[key]),
})
}
if err := handler.SetRoutes(routes...); err != nil {
for i := range routes {
log.Printf("failed: %+v", routes[i])
}
return nil, err
}
return &handler, nil
}
func (conf *HttpConfiguration) handleWithMethod(method JobHandler) func(*rest.ResponseWriter, *rest.Request) {
return func(w *rest.ResponseWriter, r *rest.Request) {
context := &HttpContext{}
context.ApiVersion = r.Header.Get("X-Api-Version")
requestId := r.Header.Get("X-Request-Id")
if requestId == "" {
context.Id = jobs.NewRequestIdentifier()
} else {
id, err := jobs.NewRequestIdentifierFromString(requestId)
if err != nil {
http.Error(w, "X-Request-Id must be a 32 character hexadecimal string", http.StatusBadRequest)
return
}
context.Id = id
}
// parse the incoming request into an object
jobRequest, errh := method(conf, context, r)
if errh != nil {
serveRequestError(w, apiRequestError{errh, errh.Error(), http.StatusBadRequest})
return
}
// find the job implementation for that request
job, errj := jobs.JobFor(jobRequest)
if errj != nil {
if errj == jobs.ErrNoJobForRequest {
serveRequestError(w, apiRequestError{errj, fmt.Sprintf("The requested job %s has no registered implementation", reflect.TypeOf(jobRequest)), http.StatusBadRequest})
}
serveRequestError(w, apiRequestError{errj, errj.Error(), http.StatusBadRequest})
return
}
// determine the type of the request
acceptHeader := r.Header.Get("Accept")
overrideAcceptHeader := r.Header.Get("X-Accept")
if overrideAcceptHeader != "" {
acceptHeader = overrideAcceptHeader
}
// setup the appropriate mode
mode := client.ResponseJson
if acceptHeader == "text/plain" {
mode = client.ResponseTable
}
canStream := didClientRequestStreamableResponse(acceptHeader)
response := NewHttpJobResponse(w.ResponseWriter, !canStream, mode)
// queue / handle the request
wait, errd := conf.Dispatcher.Dispatch(context.Id, job, response)
if errd == jobs.ErrRanToCompletion {
http.Error(w, errd.Error(), http.StatusNoContent)
return
} else if errd != nil {
serveRequestError(w, apiRequestError{errd, errd.Error(), http.StatusServiceUnavailable})
return
}
<-wait
}
}
func didClientRequestStreamableResponse(acceptHeader string) bool {
result := false
mediaTypes := strings.Split(acceptHeader, ",")
for i := range mediaTypes {
mediaType, params, _ := mime.ParseMediaType(mediaTypes[i])
result = (params["stream"] == "true") && (mediaType == "application/json" || mediaType == "text/plain")
if result {
break
}
}
return result
}
func limitedBodyReader(r *rest.Request) io.Reader {
return io.LimitReader(r.Body, 100*1024)
}
type apiRequestError struct {
Error error
Message string
Status int
}
func serveRequestError(w http.ResponseWriter, err apiRequestError) {
log.Print(err.Message, err.Error)
http.Error(w, err.Message, err.Status)
}