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
|
#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements helper procs for SCGI applictions. 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
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* {.final.} = object ## 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
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()
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)
|