forked from wasmCloud/wasmCloud
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
169 lines (144 loc) · 6.17 KB
/
main.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
159
160
161
162
163
164
165
166
167
168
169
package main
import (
"encoding/json"
"fmt"
"net/http"
"strings"
echo "github.com/wasmcloud/wasmcloud/examples/golang/components/http-echo-tinygo/gen"
)
// A struct containing information about a request,
// sent back as a response JSON from the echo server
type EchoResponse struct {
Method string `json:"method"`
Path string `json:"path"`
QueryString string `json:"queryString,omitempty"`
Body string `json:"body,omitempty"`
}
// Implmenetation struct for the `echo` world (see `wit/echo.wit`)
type Echo struct{}
type HttpRequest = echo.ExportsWasiHttp0_2_0_IncomingHandlerIncomingRequest
type HttpResponseWriter = echo.WasiHttp0_2_0_TypesResponseOutparam
type KeyValueTuple = echo.WasiHttp0_2_0_TypesTuple2FieldKeyFieldValueT
type Headers = echo.WasiHttp0_2_0_TypesHeaders
// Implementation of the `wasi-http:incoming-handler` export in the `echo` world (see `wit/echo.wit)`
//
// This method's signature and implementation use code generated by `wit-bindgen`, in the `gen` folder
// When building with `wash build`, `wit-bindgen` is run automatically to generate the classes that have been aliased above.
func (g *Echo) Handle(req HttpRequest, resp HttpResponseWriter) {
er := new(EchoResponse)
// Detect request method, use to build EchoResponse
method := req.Method()
switch method {
case echo.WasiHttp0_2_0_TypesMethodGet():
er.Method = "GET"
case echo.WasiHttp0_2_0_TypesMethodPost():
er.Method = "POST"
case echo.WasiHttp0_2_0_TypesMethodPut():
er.Method = "PUT"
case echo.WasiHttp0_2_0_TypesMethodDelete():
er.Method = "DELETE"
case echo.WasiHttp0_2_0_TypesMethodPatch():
er.Method = "PATCH"
case echo.WasiHttp0_2_0_TypesMethodConnect():
er.Method = "CONNECT"
default:
er.Method = "OTHER"
}
// Retrieve the request path w/ query fragment (an echo.Option[T])
pathWithQuery := req.PathWithQuery()
if pathWithQuery.IsNone() {
return
}
// Split the path to retrieve the query element, building the EchoResponse object
splitPathQuery := strings.Split(pathWithQuery.Unwrap(), "?")
er.Path = splitPathQuery[0]
if len(splitPathQuery) > 1 {
er.QueryString = splitPathQuery[1]
}
// Create a list of keyvalue tuples usable as response headers
// NOTE: this is a manual append because of templating
var header_tuples []KeyValueTuple
header_tuples = append(header_tuples, KeyValueTuple{F0: "Content-Type", F1: []byte("application/json")})
resp_headers := echo.StaticFieldsFromList(header_tuples).Unwrap()
// Consume the request body
maybeBody := req.Consume()
if maybeBody.IsErr() {
writeHttpResponse(resp, http.StatusInternalServerError, resp_headers, []byte("{\"error\":\"failed to read request body\"}"))
return
}
body := maybeBody.Unwrap()
// Convert the request body to a stream
maybeBodyStream := body.Stream()
if maybeBodyStream.IsErr() {
writeHttpResponse(resp, http.StatusInternalServerError, resp_headers, []byte("{\"error\":\"failed to convert body into stream\"}"))
return
}
bodyStream := maybeBodyStream.Unwrap()
// Read the maximum amount of bytes possible from the stream
maybeReadStream := bodyStream.Read(18446744073709551614)
if maybeReadStream.IsErr() {
// If the body is empty, we'll get a closed error, in which case we *do not* want to throw an error.
errKind := maybeReadStream.UnwrapErr().Kind()
if errKind == echo.WasiIo0_2_0_StreamsStreamErrorKindClosed {
// There was likely *no* data in the body (ex. a GET request)
er.Body = ""
} else {
// if we received some other error, report it
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelError(), "failed to read incoming body stream", fmt.Sprintf("error kind [%v]"))
writeHttpResponse(resp, http.StatusInternalServerError, resp_headers, []byte("{\"error\":\"failed to read incoming body stream\"}"))
return
}
} else {
// If reading from the request did not error, we can update the EchoResponse object with the request body
er.Body = string(maybeReadStream.Unwrap())
}
// Log information about the request
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelDebug(), "method", er.Method)
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelDebug(), "path", er.Path)
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelDebug(), "queryString", er.QueryString)
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelDebug(), "body", er.Body)
// Marshal the EchoResponse object we've been building to JSON
bBody, err := json.Marshal(er)
if err != nil {
writeHttpResponse(resp, http.StatusInternalServerError, resp_headers, []byte("{\"error\":\"failed to marshal response\"}"))
return
}
writeHttpResponse(resp, http.StatusOK, resp_headers, bBody)
}
// Write an outgoing HTTP response (status, headers, body) to a given writer (in WIT terms a ResponseOutparam)
func writeHttpResponse(responseOutparam HttpResponseWriter, statusCode uint16, headers Headers, body []byte) {
echo.WasiLoggingLoggingLog(echo.WasiLoggingLoggingLevelDebug(), "writeHttpResponse", "writing response: "+string(body))
// Build the new HTTP outgoing response
outgoingResponse := echo.NewOutgoingResponse(headers)
outgoingResponse.SetStatusCode(statusCode)
// Retrieve the body inside the outgoing response
maybeOutgoingBody := outgoingResponse.Body()
if maybeOutgoingBody.IsErr() {
return
}
outgoingBody := maybeOutgoingBody.Unwrap()
// Create a writable stream for the response body content
maybeOutgoingStream := outgoingBody.Write()
if maybeOutgoingStream.IsErr() {
return
}
outgoingStream := maybeOutgoingStream.Unwrap()
// Write the body to the outgoing response
res := outgoingStream.BlockingWriteAndFlush(body)
if res.IsErr() {
return
}
outgoingStream.Drop()
echo.StaticOutgoingBodyFinish(outgoingBody, echo.None[echo.WasiHttp0_2_0_TypesTrailers]())
// Set the response on the outparam
echo.StaticResponseOutparamSet(responseOutparam, echo.Ok[echo.WasiHttp0_2_0_TypesOutgoingResponse, echo.WasiHttp0_2_0_TypesErrorCode](outgoingResponse))
}
func init() {
mg := new(Echo)
echo.SetExportsWasiHttp0_2_0_IncomingHandler(mg)
}
// NOTE: the below go-generate line is not strictly necessary when using `wash build`,
// but it enables use with the `go` and Bytecode Alliance `wit-bindgen` tooling
//
//go:generate wit-bindgen tiny-go wit --out-dir=gen --gofmt
func main() {}