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
|
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements helper procs for SCGI applications. Example:
##
## .. code-block:: Nimrod
##
## import strtabs, sockets, scgi
##
## var counter = 0
## proc handleRequest(client: TSocket, input: string,
## headers: PStringTable): bool {.procvar.} =
## inc(counter)
## client.writeStatusOkTextContent()
## client.send("Hello for the $#th time." % $counter & "\c\L")
## return false # do not stop processing
##
## run(handleRequest)
##
import sockets, strutils, os, strtabs, asyncio
type
EScgi* = object of EIO ## the exception that is raised, if a SCGI error occurs
proc scgiError*(msg: string) {.noreturn.} =
## raises an EScgi exception with message `msg`.
var e: ref EScgi
new(e)
e.msg = msg
raise e
proc parseWord(inp: string, outp: var string, start: int): int =
result = start
while inp[result] != '\0': inc(result)
outp = substr(inp, start, result-1)
proc parseHeaders(s: string, L: int): PStringTable =
result = newStringTable()
var i = 0
while i < L:
var key, val: string
i = parseWord(s, key, i)+1
i = parseWord(s, val, i)+1
result[key] = val
if s[i] == ',': inc(i)
else: scgiError("',' after netstring expected")
proc recvChar(s: TSocket): char =
var c: char
if recv(s, addr(c), sizeof(c)) == sizeof(c):
result = c
type
TScgiState* = object of TObject ## SCGI state object
server: TSocket
bufLen: int
client*: TSocket ## the client socket to send data to
headers*: PStringTable ## the parsed headers
input*: string ## the input buffer
TAsyncScgiState* = object of TScgiState
handleRequest: proc (server: var TAsyncScgiState, client: TSocket,
input: string, headers: PStringTable,userArg: PObject)
userArg: PObject
PAsyncScgiState* = ref TAsyncScgiState
proc recvBuffer(s: var TScgiState, L: int) =
if L > s.bufLen:
s.bufLen = L
s.input = newString(L)
if L > 0 and recv(s.client, cstring(s.input), L) != L:
scgiError("could not read all data")
setLen(s.input, L)
proc open*(s: var TScgiState, port = TPort(4000), address = "127.0.0.1") =
## opens a connection.
s.bufLen = 4000
s.input = newString(s.buflen) # will be reused
s.server = socket()
if s.server == InvalidSocket: scgiError("could not open socket")
#s.server.connect(connectionName, port)
bindAddr(s.server, port, address)
listen(s.server)
proc close*(s: var TScgiState) =
## closes the connection.
s.server.close()
proc next*(s: var TScgistate, timeout: int = -1): bool =
## proceed to the first/next request. Waits ``timeout`` miliseconds for a
## request, if ``timeout`` is `-1` then this function will never time out.
## Returns `True` if a new request has been processed.
var rsocks = @[s.server]
if select(rsocks, timeout) == 1 and rsocks.len == 0:
s.client = accept(s.server)
var L = 0
while true:
var d = s.client.recvChar()
if d notin strutils.digits:
if d != ':': scgiError("':' after length expected")
break
L = L * 10 + ord(d) - ord('0')
recvBuffer(s, L+1)
s.headers = parseHeaders(s.input, L)
if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
L = parseInt(s.headers["CONTENT_LENGTH"])
recvBuffer(s, L)
return True
proc writeStatusOkTextContent*(c: TSocket, contentType = "text/html") =
## sends the following string to the socket `c`::
##
## Status: 200 OK\r\LContent-Type: text/html\r\L\r\L
##
## You should send this before sending your HTML page, for example.
c.send("Status: 200 OK\r\L" &
"Content-Type: $1\r\L\r\L" % contentType)
proc run*(handleRequest: proc (client: TSocket, input: string,
headers: PStringTable): bool,
port = TPort(4000)) =
## encapsulates the SCGI object and main loop.
var s: TScgiState
s.open(port)
var stop = false
while not stop:
if next(s):
stop = handleRequest(s.client, s.input, s.headers)
s.client.close()
s.close()
proc open*(handleRequest: proc (server: var TAsyncScgiState, client: TSocket,
input: string, headers: PStringTable,
userArg: PObject),
port = TPort(4000), address = "127.0.0.1",
userArg: PObject = nil): PAsyncScgiState =
## Alternative of ``open`` for asyncio compatible SCGI.
new(result)
open(result[], port, address)
result.handleRequest = handleRequest
result.userArg = userArg
proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] =
var s = PAsyncScgiState(h)
return (SockListening, s.server)
proc handleAccept(h: PObject) =
var s = PAsyncScgiState(h)
s.client = accept(s.server)
var L = 0
while true:
var d = s.client.recvChar()
if d notin strutils.digits:
if d != ':': scgiError("':' after length expected")
break
L = L * 10 + ord(d) - ord('0')
recvBuffer(s[], L+1)
s.headers = parseHeaders(s.input, L)
if s.headers["SCGI"] != "1": scgiError("SCGI Version 1 expected")
L = parseInt(s.headers["CONTENT_LENGTH"])
recvBuffer(s[], L)
s.handleRequest(s[], s.client, s.input, s.headers, s.userArg)
proc register*(d: PDispatcher, s: PAsyncScgiState) =
## Registers ``s`` with dispatcher ``d``.
var dele = newDelegate()
dele.deleVal = s
dele.getSocket = getSocket
dele.handleAccept = handleAccept
d.register(dele)
when false:
var counter = 0
proc handleRequest(client: TSocket, input: string,
headers: PStringTable): bool {.procvar.} =
inc(counter)
client.writeStatusOkTextContent()
client.send("Hello for the $#th time." % $counter & "\c\L")
return false # do not stop processing
run(handleRequest)
|