Skip to content

Commit bd55cf4

Browse files
committed
Updated documentation
1 parent 24fec6a commit bd55cf4

File tree

3 files changed

+110
-33
lines changed

3 files changed

+110
-33
lines changed

README.md

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This repository contains a generic HTTP client which can be adapted to provide:
77
* Ability to send files and data of type `multipart/form-data`
88
* Ability to send data of type `application/x-www-form-urlencoded`
99
* Debugging capabilities to see the request and response data
10-
* Streaming JSON responses
10+
* Streaming text events
1111

1212
API Documentation: https://pkg.go.dev/github.com/mutablelogic/go-client
1313

@@ -58,28 +58,31 @@ func main() {
5858
Various options can be passed to the client `New` method to control its behaviour:
5959

6060
* `OptEndpoint(value string)` sets the endpoint for all requests
61-
* `OptTimeout(value time.Duration)` sets the timeout on any request, which defaults to 30 seconds
62-
* `OptUserAgent(value string)` sets the user agent string on each API request
63-
* `OptTrace(w io.Writer, verbose bool)` allows you to debug the request and response data.
64-
When `verbose` is set to true, it also displays the payloads
65-
* `OptStrict()` turns on strict content type checking on anything returned from the API
66-
* `OptRateLimit(value float32)` sets the limit on number of requests per second and the API will sleep to regulate
67-
the rate limit when exceeded
68-
* `OptReqToken(value Token)` sets a request token for all client requests. This can be overridden by the client
69-
for individual requests using `OptToken`
70-
* `OptSkipVerify()` skips TLS certificate domain verification
71-
* `OptHeader(key, value string)` appends a custom header to each request
61+
* `OptTimeout(value time.Duration)` sets the timeout on any request, which defaults to 30 seconds.
62+
Timeouts can be ignored on a request-by-request basis using the `OptNoTimeout` option (see below).
63+
* `OptUserAgent(value string)` sets the user agent string on each API request.
64+
* `OptTrace(w io.Writer, verbose bool)` allows you to debug the request and response data.
65+
When `verbose` is set to true, it also displays the payloads.
66+
* `OptStrict()` turns on strict content type checking on anything returned from the API.
67+
* `OptRateLimit(value float32)` sets the limit on number of requests per second and the API
68+
will sleep to regulate the rate limit when exceeded.
69+
* `OptReqToken(value Token)` sets a request token for all client requests. This can be
70+
overridden by the client for individual requests using `OptToken` (see below).
71+
* `OptSkipVerify()` skips TLS certificate domain verification.
72+
* `OptHeader(key, value string)` appends a custom header to each request.
7273

7374
## Usage with a payload
7475

75-
The first argument to the `Do` method is the payload to send to the server, when set. You can create a payload
76-
using the following methods:
76+
The first argument to the `Do` method is the payload to send to the server, when set.
77+
You can create a payload using the following methods:
7778

7879
* `client.NewRequest()` returns a new empty payload which defaults to GET.
79-
* `client.NewJSONRequest(payload any, accept string)` returns a new request with a JSON payload which defaults to POST.
80-
* `client.NewMultipartRequest(payload any, accept string)` returns a new request with a Multipart Form data payload which
81-
defaults to POST.
82-
* `client.NewFormRequest(payload any, accept string)` returns a new request with a Form data payload which defaults to POST.
80+
* `client.NewJSONRequest(payload any, accept string)` returns a new request with
81+
a JSON payload which defaults to POST.
82+
* `client.NewMultipartRequest(payload any, accept string)` returns a new request with
83+
a Multipart Form data payload which defaults to POST.
84+
* `client.NewFormRequest(payload any, accept string)` returns a new request with a
85+
Form data payload which defaults to POST.
8386

8487
For example,
8588

@@ -131,7 +134,7 @@ type Payload interface {
131134

132135
## Request options
133136

134-
The signature of the `Do` method is:
137+
The signature of the `Do` method is as follows:
135138

136139
```go
137140
type Client interface {
@@ -143,16 +146,19 @@ type Client interface {
143146
}
144147
```
145148

146-
Various options can be passed to modify each individual request when using the `Do` method:
149+
If you pass a context to the `DoWithContext` method, then the request can be
150+
cancelled using the context in addition to the timeout. Various options can be passed to
151+
modify each individual request when using the `Do` method:
147152

148153
* `OptReqEndpoint(value string)` sets the endpoint for the request
149154
* `OptPath(value ...string)` appends path elements onto a request endpoint
150155
* `OptToken(value Token)` adds an authorization header (overrides the client OptReqToken option)
151156
* `OptQuery(value url.Values)` sets the query parameters to a request
152-
* `OptHeader(key, value string)` appends a custom header to the request
153-
* `OptResponse(func() error)` allows you to set a callback function to process a streaming response.
154-
See below for more details.
157+
* `OptHeader(key, value string)` sets a custom header to the request
155158
* `OptNoTimeout()` disables the timeout on the request, which is useful for long running requests
159+
* `OptTextStreamCallback(func(TextStreamCallback) error)` allows you to set a callback
160+
function to process a streaming text response of type `text/event-stream`. See below for
161+
more details.
156162

157163
## Authentication
158164

@@ -185,10 +191,45 @@ You can also set the token on a per-request basis using the `OptToken` option in
185191

186192
You can create a payload with form data:
187193

188-
* `client.NewFormRequest(payload any, accept string)` returns a new request with a Form data payload which defaults to POST.
189-
* `client.NewMultipartRequest(payload any, accept string)` returns a new request with a Multipart Form data payload which defaults to POST. This is useful for file uploads.
194+
* `client.NewFormRequest(payload any, accept string)` returns a new request with a Form
195+
data payload which defaults to POST.
196+
* `client.NewMultipartRequest(payload any, accept string)` returns a new request with
197+
a Multipart Form data payload which defaults to POST. This is useful for file uploads.
190198

191-
The payload should be a `struct` where the fields are converted to form tuples. File uploads require a field of type `multipart.File`.
199+
The payload should be a `struct` where the fields are converted to form tuples. File uploads require a field of type `multipart.File`. For example,
200+
201+
```go
202+
package main
203+
204+
import (
205+
client "github.com/mutablelogic/go-client"
206+
multipart "github.com/mutablelogic/go-client/pkg/multipart"
207+
)
208+
209+
type FileUpload struct {
210+
File multipart.File `json:"file"`
211+
}
212+
213+
func main() {
214+
// Create a new client
215+
c := client.New(client.OptEndpoint("https://api.example.com/api/v1"))
216+
217+
// Create a file upload request
218+
request := FileUpload{
219+
File: multipart.File{
220+
Path: "helloworld.txt",
221+
Body: strings.NewReader("Hello, world!"),
222+
}
223+
}
224+
225+
// Upload a file
226+
if payload, err := client.NewMultipartRequest(request, "*/*"); err != nil {
227+
// Handle error
228+
} else if err := c.Do(payload, &response, OptPath("upload")); err != nil {
229+
// Handle error
230+
}
231+
}
232+
```
192233

193234
## Unmarshalling responses
194235

@@ -202,6 +243,32 @@ type Unmarshaler interface {
202243

203244
## Streaming Responses
204245

205-
If the returned content is a stream of JSON responses, then you can use the `OptResponse(fn func() error)` option, which
206-
will be called by the `Do` method for each response. The function should return an error if the stream should be terminated.
246+
The client implements a streaming text event callback which can be used to process a stream of text events, as per the [Mozilla specification](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events).
247+
248+
In order to process streamed events, pass the `OptTextStreamCallback()` option to the request
249+
with a callback function, which should have the following signature:
250+
251+
```go
252+
func Callback(event client.TextStreamEvent) error {
253+
// Finish processing successfully
254+
if event.Event == "close" {
255+
return io.EOF
256+
}
257+
258+
// Decode the data into a JSON object
259+
var data map[string]any
260+
if err := event.Json(data); err != nil {
261+
return err
262+
}
263+
264+
// Return success - continue streaming
265+
return nil
266+
}
267+
```
268+
269+
The `TextStreamEvent` object has the following
270+
271+
If you return an error of type `io.EOF` from the callback, then the stream will be closed.
272+
Similarly, if you return any other error the stream will be closed and the error returned.
273+
207274
Usually, you would pair this option with `OptNoTimeout` to prevent the request from timing out.

requestopts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func OptQuery(value url.Values) RequestOpt {
7979
}
8080
}
8181

82-
// OptReqHeader adds a header value to the request
82+
// OptReqHeader sets a header value to the request
8383
func OptReqHeader(name, value string) RequestOpt {
8484
return func(r *requestOpts) error {
8585
r.Header.Set(name, value)

textstream.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,17 @@ type TextStream struct {
2626
// Implementation of a text stream, as per
2727
// https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#event_stream_format
2828
type TextStreamEvent struct {
29-
Id string `json:"id,omitempty"`
30-
Event string `json:"event,omitempty"`
31-
Data string `json:"data"`
29+
// The event ID to set the EventSource object's last event ID value.
30+
Id string `json:"id,omitempty"`
31+
32+
// A string identifying the type of event described
33+
Event string `json:"event,omitempty"`
34+
35+
// The data field for the message
36+
Data string `json:"data"`
37+
38+
// The reconnection time. If the connection to the server is lost,
39+
// the client should wait for the specified time before attempting to reconnect.
3240
Retry time.Duration `json:"retry,omitempty"`
3341
}
3442

@@ -39,6 +47,7 @@ type TextStreamCallback func(TextStreamEvent) error
3947
// GLOBALS
4048

4149
const (
50+
// Mime type for text stream
4251
ContentTypeTextStream = "text/event-stream"
4352
)
4453

@@ -123,7 +132,8 @@ func (t *TextStream) Decode(r io.Reader, callback TextStreamCallback) error {
123132
case "event":
124133
event.Event = value
125134
case "data":
126-
event.Data = value
135+
// Concatenate data
136+
event.Data = event.Data + value
127137
case "retry":
128138
// Retry time in milliseconds, ignore if not a number
129139
if retry, err := strconv.ParseInt(value, 10, 64); err == nil {

0 commit comments

Comments
 (0)