This example illustrates the use of GRIP to stream HTTP responses using a Remix application as the backend.
For instructions on setting up and running the example, either locally or using
Fastly Fanout as the GRIP proxy, refer to the README
file in the parent directory.
This example also requires curl
, which is included with most OSes.
After you have set up Pushpin and started the application, test the example following these steps.
NOTE: If you are using Fastly Fanout as the GRIP proxy, follow these steps, but replace
127.0.0.1:7999
with the public URL of your Fanout Forwarding service.
- Open a new terminal window, and type the following:
curl http://127.0.0.1:7999/api/stream
You should see the following response text, and then the response should hang open:
[stream open]
curl
now has an open HTTP stream, held open by Pushpin (listening on a channel internally called test
).
- Open a separate terminal window, and type the following:
curl -X POST -d "Hello" "https://127.0.0.1:7999/api/broadcast"
This publishes the given message (to the channel test
). You should see the message Hello
appear in the stream held open by the first terminal.
For an explanation of the common startup and initialization code, as well as
validating the GRIP header, refer to the README
file in the parent
directory.
The example exposes two API Routes (Resource Routes):
- A
GET
request at/api/stream
(File:app/routes/api.stream.ts
)
This endpoint is intended to be called through your configured GRIP proxy.
The handler calls publisher.validateGripSig
to validate this header, storing the result in
the gripStatus
variable.
It checks gripStatus.isProxied
to make sure we are being run behind a valid
GRIP proxy. This value will be false
if the request did not come through a GRIP proxy,
or if the signature validation failed.
if (!gripStatus.isProxied) {
// emit an error
}
If successful, then the handler goes on to set up a GRIP instruction.
This instruction asks the GRIP proxy to hold the current connection open
as a streaming connection, listening to the channel named 'test'
.
const gripInstruct = new GripInstruct('test');
gripInstruct.setHoldStream();
Finally, a response is generated and returned, including the
gripInstruct
in the response headers.
return new Response(
'[stream open]\n',
{
status: 200,
headers: {
...gripInstruct.toHeaders(),
'Content-Type': 'text/plain',
},
},
);
That's all that's needed to hold a connection open. Note that the connection between your backend and the GRIP proxy ends here. After this point, the GRIP proxy holds the connection open with the client.
- A
POST
request at/api/publish
(File:app/routes/api.publish.ts
)
This handler starts by checking to make sure the method is in fact POST
and that
the content type header specifies that the body is of type text/plain
. Afterward,
the handler reads the request body into a string.
if (request.method !== 'POST') {
// emit an error
}
if (request.headers.get('content-type')?.split(';')[0] !== 'text/plain') {
// emit an error
}
const body = await request.text();
Next, the handler proceeds to call publisher.publishHttpStream
to send the
body text as a message to the test
channel. Any listener on this channel,
such as those that have opened a stream through the endpoint described above,
will receive the message.
await publisher.publishHttpStream('test', body + '\n');
Finally, it returns a simple success response message and ends.
return new Response(
'Ok\n',
{
status: 200,
headers: {
'Content-Type': 'text/plain',
},
},
);