forked from 30x/libgozerian
-
Notifications
You must be signed in to change notification settings - Fork 0
/
gobridge.go
295 lines (253 loc) · 8.52 KB
/
gobridge.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
package main
import (
"sync"
"unsafe"
)
/*
#include <stdlib.h>
*/
import "C"
const (
// URNScheme is the standard "URN" URI scheme
URNScheme = "urn"
urnPrefix = URNScheme + ":"
// TestHandlerURIName is used to construct TestHandlerURI
TestHandlerURIName = "weaver-proxy:unit-test"
// BadHandlerURIName is used to construct BadHandlerURI
BadHandlerURIName = "weaver-proxy:always-bad"
// TestHandlerURI always refers to a special pipeline that does various things
// for the purposes of unit testing libgozerian.
TestHandlerURI = urnPrefix + TestHandlerURIName
// BadHandlerURI always refers to a non-existent handler and is used for unit
// testing.
BadHandlerURI = urnPrefix + BadHandlerURIName
)
// A global, thread-safe chunk table.
type chunk struct {
id int32
len uint32
data unsafe.Pointer
}
var lastChunkID int32 = 1
var chunks = make(map[int32]chunk)
var chunkLock = sync.Mutex{}
// This is the actual C language interface to weaver. It is basically
// a small C wrapper to the "manager."
// Functions below are the public C-language API for this code.
/*
GoCreateHandler creates a new handler. This tells the GO implementation to set up some
resources for handling requests later. If there was an error creating
the handler, then return a string indicating the cause. Otherwise,
return NULL. If a string is returned, the caller must free it using "free".
The first parameter is a string ID that will be used later to refer to the handler.
The content of this string is up to the caller.
The second parameter is a URI to the configuration. The contents of this URI
are up to the Gozerian library.
*/
//export GoCreateHandler
func GoCreateHandler(handlerID, configURI *C.char) *C.char {
err := createHandler(C.GoString(handlerID), C.GoString(configURI))
if err == nil {
return nil
}
return C.CString(err.Error())
}
/*
GoDestroyHandler destroys a handler created by GoCreateHandler.
Depending on how libgozerian is used it may be important to call
this when finished with a handler.
The parameter is the same handler ID that was passed to GoCreateHandler.
*/
//export GoDestroyHandler
func GoDestroyHandler(handlerID *C.char) {
destroyHandler(C.GoString(handlerID))
}
/*
GoCreateRequest creates a new "request" object and return its unique ID. The request
goes in a map, so it's important that the caller always call
GoFreeRequest or there will be a memory leak.
The parameter is the same handler ID that was passed to GoCreateHandler.
This method returns a request ID that must be used later to identify
the request.
*/
//export GoCreateRequest
func GoCreateRequest(handlerID *C.char) uint32 {
return createRequest(C.GoString(handlerID))
}
/*
GoCreateResponse creates a new "response" object and return its unique ID.
Like the request the response goes in a map and must be freed.
This method returns a response ID that must be used later to identify
the response.
*/
//export GoCreateResponse
func GoCreateResponse(handlerID *C.char) uint32 {
return createResponse(C.GoString(handlerID))
}
/*
GoFreeRequest cleans up any storage used by the request. This method must be called for
every ID generated by GoCreateRequest or there will be a memory leak.
*/
//export GoFreeRequest
func GoFreeRequest(id uint32) {
freeRequest(id)
}
/*
GoFreeResponse cleans the response like the request.
*/
//export GoFreeResponse
func GoFreeResponse(id uint32) {
freeResponse(id)
}
/*
GoStoreChunk stores a chunk of data. The pointer must already have been allocated
using "malloc" and the data must be valid for the length of the
request. A chunk ID will be returned.
*/
//export GoStoreChunk
func GoStoreChunk(data unsafe.Pointer, len uint32) int32 {
chunkLock.Lock()
defer chunkLock.Unlock()
lastChunkID++
if lastChunkID < 0 {
lastChunkID = 1
}
c := chunk{
id: lastChunkID,
len: len,
data: data,
}
chunks[lastChunkID] = c
return lastChunkID
}
/*
GoReleaseChunk frees a chunk of data that was stored using GoStoreChunk. This only frees
the data used to track the chunk -- the caller is responsible for
actually calling "free".
*/
//export GoReleaseChunk
func GoReleaseChunk(id int32) {
releaseChunk(id)
}
/*
GoGetChunk retrieves the pointer to a chunk of data stored using "GoStoreChunk".
*/
//export GoGetChunk
func GoGetChunk(id int32) unsafe.Pointer {
return getChunk(id).data
}
/*
GoGetChunkLength retrieves the length of a specific chunk.
*/
//export GoGetChunkLength
func GoGetChunkLength(id int32) uint32 {
return getChunk(id).len
}
func getChunk(id int32) chunk {
chunkLock.Lock()
defer chunkLock.Unlock()
return chunks[id]
}
func releaseChunk(id int32) {
chunkLock.Lock()
defer chunkLock.Unlock()
delete(chunks, id)
}
/*
GoBeginRequest starts parsing the new request. The first parameter is the
request ID returned by "GoCreateRequest."
The second parameter must be a string that
represents the HTTP request line and headers, separated by CRLF pairs,
exactly as described in the HTTP spec.
Once this function has been called, the request is already running.
The caller MUST periodically call "GoPollRequest" in order to get updates
on the status of the request, and MUST call "GoFreeRequest" after
the request is done.
*/
//export GoBeginRequest
func GoBeginRequest(id uint32, rawHeaders *C.char) {
beginRequest(id, C.GoString(rawHeaders))
}
/*
GoPollRequest polls for updates from the running request. Each update is returned as
a null-terminated string. The format of each command string is
described in the README.
The first parameter is a request ID. If the second parameter ("block") is
non-zero, then this method will block the calling thread until data is
available. Otherwise, it will immediately return NULL if there is nothing
to report. Implementations are strongly advised to add a short delay between
polling requests if blocking cannot be used.
The final response from the request will be "DONE." When this is called,
then no more commands will be returned. The caller must not poll
after "DONE" is returned.
The caller is responsible for calling "free" on the returned command string.
*/
//export GoPollRequest
func GoPollRequest(id uint32, block int32) *C.char {
cmd := pollRequest(id, block != 0)
if cmd == "" {
return nil
}
return C.CString(cmd)
}
/*
GoSendRequestBodyChunk sends a chunk of request data to the running request.
This method must not be called until GoPollRequest returns an RBOD command.
The first parameter is the request ID. The second, if non-zero, indicates that
this chunk is the last chunk of data in the request body. If this method is
never called with this parameter set to non-zero, then the request will likely
never complete correctly.
The last two parameters are a pointer to the data itself, and the length.
A copy will be made before this function
call returns, so the caller is free to deallocate this memory
immediately after this function returns
*/
//export GoSendRequestBodyChunk
func GoSendRequestBodyChunk(id uint32, l int32, data unsafe.Pointer, len uint32) {
buf, last := copyPointer(l, data, len)
sendRequestBodyChunk(id, last, buf)
}
/*
GoBeginResponse starts to handle a response. Like the request path, this starts response
handling working in a goroutine, and the caller may poll for changes
using GoPollResponse.
The first parameter is the response ID as created by GoCreateResponse. The second
is the request ID that was previously used for the request side of this interaction.
The third is the current HTTP status code of the response, while the last is a
set of headers encoded in the same format used by the WHDR command: "name: value"
lines separated by a single newline (not a CRLF as in HTTP).
*/
//export GoBeginResponse
func GoBeginResponse(responseID, requestID, status uint32, hdrs *C.char) {
beginResponse(responseID, requestID, status, C.GoString(hdrs))
}
// GoPollResponse returns response commands just like request commands.
//export GoPollResponse
func GoPollResponse(id uint32, block int32) *C.char {
cmd := pollResponse(id, block != 0)
if cmd == "" {
return nil
}
return C.CString(cmd)
}
// GoSendResponseBodyChunk sends a chunk for the response body just like for the
// request body.
//export GoSendResponseBodyChunk
func GoSendResponseBodyChunk(id uint32, l int32, data unsafe.Pointer, len uint32) {
buf, last := copyPointer(l, data, len)
sendResponseBodyChunk(id, last, buf)
}
func copyPointer(l int32, data unsafe.Pointer, len uint32) ([]byte, bool) {
buf := C.GoBytes(data, C.int(len))
var last bool
if l != 0 {
last = true
}
return buf, last
}
func sliceToPtr(buf []byte) (unsafe.Pointer, uint32) {
l := C.size_t(len(buf))
ptr := C.malloc(l)
copy((*[1 << 30]byte)(ptr)[:], buf)
return ptr, uint32(l)
}