-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathserver.go
147 lines (127 loc) · 3.66 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
package fedwiki
import (
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"path"
"strings"
)
// errorInfo contains relevant information about an error
type errorInfo struct {
Status string
Code int
Detail string
}
// ErrorResponse creates a response based on http Error code
func ErrorResponse(ecode int, format string, args ...interface{}) (code int, template string, data interface{}) {
return ecode, "error", errorInfo{
Status: http.StatusText(ecode),
Code: ecode,
Detail: fmt.Sprintf(format, args...),
}
}
// Template is the interface that is used to render pages as HTML
type Template interface {
// RenderHTML renders data as HTML with the appropriate template
// template = "" means that it should be rendered as a regular page
RenderHTML(w io.Writer, template string, data interface{}) error
}
// Handler is the interface for handling federated wiki requests
type Handler interface {
Handle(r *http.Request) (code int, template string, data interface{})
}
// HandlerFunc type adapts a function to be used as a regular handler
type HandlerFunc func(r *http.Request) (code int, template string, data interface{})
func (fn HandlerFunc) Handle(r *http.Request) (code int, template string, data interface{}) {
return fn(r)
}
// This implements basic management of request of headers and canonicalizes the requests
type Server struct {
Handler Handler
Template Template
}
func (server *Server) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
responseType := ""
if r.Header.Get("Accept") != "" {
h := parseAccept(r)
switch {
case h.Accepts("*/*"):
fallthrough
case h.Accepts("application/json"):
responseType = "application/json"
case h.Accepts("text/html"):
responseType = "text/html"
case h.Accepts("text/plain"):
responseType = "text/plain"
default:
http.Error(rw, fmt.Sprintf(`Unknown Accept header "%s".`, r.Header.Get("Accept")), http.StatusNotAcceptable)
return
}
}
// handle explicit extensions
ext := path.Ext(r.URL.Path)
switch ext {
case ".json":
r.URL.Path = r.URL.Path[:len(r.URL.Path)-len(ext)]
responseType = "application/json"
case ".html":
r.URL.Path = r.URL.Path[:len(r.URL.Path)-len(ext)]
responseType = "text/html"
}
switch {
case responseType == "" && server.Template == nil:
responseType = "application/json"
case responseType == "":
responseType = "text/html"
case responseType == "text/html" && server.Template == nil:
responseType = "application/json"
}
// Redirect if it isn't a valid slug
slug := Slugify(r.URL.Path)
if string(slug) != r.URL.Path {
http.Redirect(rw, r, string(slug), http.StatusSeeOther)
return
}
code, template, data := server.Handler.Handle(r)
rw.Header().Set("Content-Type", responseType)
rw.WriteHeader(code)
switch responseType {
case "application/json":
json.NewEncoder(rw).Encode(data)
case "text/plain":
fmt.Fprintf(rw, "%#v\n", data)
case "text/html":
err := server.Template.RenderHTML(rw, template, data)
if err != nil {
fmt.Fprintf(rw, err.Error())
}
default:
fmt.Fprintf(rw, fmt.Sprintf("Unknown Content-Type \"%v\"", responseType))
}
}
type acceptHeaders []string
// Accepts checks whether mimetype is allowed
func (spec acceptHeaders) Accepts(mimetype string) bool {
for _, mtype := range spec {
if mtype == mimetype {
return true
}
}
return false
}
// parseAccept parses request Accept header
func parseAccept(r *http.Request) acceptHeaders {
var spec acceptHeaders
accepts := r.Header.Get("Accept")
params := strings.Split(accepts, ";")
for _, accept := range strings.Split(params[0], ",") {
m, _, err := mime.ParseMediaType(accept)
if err != nil {
continue
}
spec = append(spec, strings.TrimSpace(m))
}
return spec
}