examples: start of a gemini server
FossilOrigin-Name: fc550c205d4d56285e9f8d14ddf37f70d5ddf4dce88f595b98ae84d95e49764d
This commit is contained in:
parent
efa8eba21d
commit
1f7d3091b8
1 changed files with 183 additions and 0 deletions
183
example/atua-gemini.retro
Normal file
183
example/atua-gemini.retro
Normal file
|
@ -0,0 +1,183 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd /home/crc/gemini
|
||||
retro atua-gemini
|
||||
exit
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
This is Atua-Gemini, my Gemini server in RetroForth.
|
||||
|
||||
As with most of my servers, it runs under inetd, and is only
|
||||
built to meet my needs.
|
||||
|
||||
The relevant parts of the Gemini spec are included here. I'm
|
||||
using 0.14.3, taken from
|
||||
https://gemini.circumlunar.space/docs/specification.html
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
1.1 Gemini transactions
|
||||
|
||||
There is one kind of Gemini transaction, roughly equivalent to
|
||||
a gopher request or a HTTP "GET" request. Transactions happen
|
||||
as follows:
|
||||
|
||||
C: Opens connection
|
||||
S: Accepts connection
|
||||
C/S: Complete TLS handshake (see section 4)
|
||||
C: Validates server certificate (see 4.2)
|
||||
C: Sends request (one CRLF terminated line) (see section 2)
|
||||
S: Sends response header (one CRLF terminated line), closes
|
||||
connection under non-success conditions (see 3.1 & 3.2)
|
||||
S: Sends response body (text or binary data) (see 3.3)
|
||||
S: Closes connection
|
||||
C: Handles response (see 3.4)
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
~~~
|
||||
:eol ASCII:CR ASCII:LF [ c:put ] bi@ ;
|
||||
~~~
|
||||
|
||||
|
||||
I am delegating the connection handling to inetd and the TLS
|
||||
handling to ssltunnel. So all I need to handle is the request
|
||||
itself.
|
||||
|
||||
Reading the request is just a matter of:
|
||||
|
||||
~~~
|
||||
'Request var
|
||||
:request s:get s:keep &Request store ;
|
||||
~~~
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
1.2 Gemini URI scheme
|
||||
|
||||
Resources hosted via Gemini are identified using URIs with the
|
||||
scheme "gemini". This scheme is syntactically compatible with
|
||||
the generic URI syntax defined in RFC 3986, but does not
|
||||
support all components of the generic syntax. In particular,
|
||||
the authority component is allowed and required, but its
|
||||
userinfo subcomponent is NOT allowed. The host subcomponent
|
||||
is required. The port subcomponent is optional, with a default
|
||||
value of 1965. The path, query and fragment components are
|
||||
allowed and have no special meanings beyond those defined by
|
||||
the generic syntax. Spaces in gemini URIs should be encoded as
|
||||
%20, not +.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
From the RFC 3986:
|
||||
|
||||
The following are two example URIs and their component parts:
|
||||
|
||||
foo://example.com:8042/over/there?name=ferret#nose
|
||||
\_/ \______________/\_________/ \_________/ \__/
|
||||
| | | | |
|
||||
scheme authority path query fragment
|
||||
| _____________________|__
|
||||
/ \ / \
|
||||
urn:example:animal:ferret:nose
|
||||
|
||||
~~~
|
||||
'Scheme var
|
||||
'Authority var
|
||||
'Port var
|
||||
'Path var
|
||||
'Query var
|
||||
'Fragment var
|
||||
|
||||
:uri:parse #9 + s:keep !Path ;
|
||||
~~~
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
3 Gemini responses
|
||||
|
||||
Gemini response consist of a single CRLF-terminated header
|
||||
line, optionally followed by a response body.
|
||||
|
||||
3.1 Response headers
|
||||
|
||||
Gemini response headers look like this:
|
||||
|
||||
<STATUS><SPACE><META><CR><LF>
|
||||
|
||||
<STATUS> is a two-digit numeric status code, as described
|
||||
below in 3.2 and in Appendix 1.
|
||||
|
||||
<SPACE> is a single space character, i.e. the byte 0x20.
|
||||
|
||||
<META> is a UTF-8 encoded string of maximum length 1024
|
||||
bytes, whose meaning is <STATUS> dependent.
|
||||
|
||||
<STATUS> and <META> are separated by a single space
|
||||
character.
|
||||
|
||||
If <STATUS> does not belong to the "SUCCESS" range of codes,
|
||||
then the server MUST close the connection after sending the
|
||||
header and MUST NOT send a response body.
|
||||
|
||||
If a server sends a <STATUS> which is not a two-digit number or
|
||||
a <META> which exceeds 1024 bytes in length, the client SHOULD
|
||||
close the connection and disregard the response header,
|
||||
informing the user of an error.
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
I define constants for the statuses. Most of these won't be
|
||||
used by this server.
|
||||
|
||||
~~~
|
||||
{ { '10 'status:INPUT }
|
||||
{ '11 'status:SENSITIVE-INPUT }
|
||||
{ '20 'status:SUCCESS }
|
||||
{ '30 'status:REDIRECT-TEMPORARY }
|
||||
{ '31 'status:REDIRECT-PERMANENT }
|
||||
{ '40 'status:TEMPORARY-FAILURE }
|
||||
{ '41 'status:SERVER-UNAVAILABLE }
|
||||
{ '42 'status:CGI-ERROR }
|
||||
{ '43 'status:PROXY-ERROR }
|
||||
{ '44 'status:SLOW-DOWN }
|
||||
{ '50 'status:PERMANENT-FAILURE }
|
||||
{ '51 'status:NOT-FOUND }
|
||||
{ '52 'status:GONE }
|
||||
{ '53 'status:PROXY-REQUEST-REFUSED }
|
||||
{ '59 'status:BAD-REQUEST }
|
||||
{ '60 'status:CLIENT-CERTIFICATE-REQUIRED }
|
||||
{ '61 'status:CERTIFICATE-NOT-AUTHORISED }
|
||||
{ '62 'status:CERTIFICATE-NOT-VALID }
|
||||
} [ [ ] a:for-each s:const ] a:for-each
|
||||
~~~
|
||||
|
||||
|
||||
~~~
|
||||
:status s:put ;
|
||||
:meta s:put ;
|
||||
:response status sp meta eol ;
|
||||
~~~
|
||||
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
~~~
|
||||
:process
|
||||
@Request '/ s:ends-with? [ @Request 'index.gmi s:append s:keep !Request ] if
|
||||
@Request '.com s:ends-with? [ @Request '/index.gmi s:append s:keep !Request ] if
|
||||
@Request uri:parse
|
||||
@Path file:exists? [ FALSE 'Resource_not_found:_ @Path s:append status:NOT-FOUND ] -if;
|
||||
here @Path file:slurp here TRUE 'text/gemini status:SUCCESS ;
|
||||
~~~
|
||||
|
||||
|
||||
---------------------------------------------------------------
|
||||
|
||||
~~~
|
||||
:serve
|
||||
request process response [ s:put ] if ;
|
||||
|
||||
serve
|
||||
~~~
|
Loading…
Reference in a new issue