sl@0
|
1 |
#
|
sl@0
|
2 |
# The httpd_ procedures implement a stub http server.
|
sl@0
|
3 |
#
|
sl@0
|
4 |
# Copyright (c) 1997-1998 Sun Microsystems, Inc.
|
sl@0
|
5 |
# Copyright (c) 1999-2000 Scriptics Corporation
|
sl@0
|
6 |
#
|
sl@0
|
7 |
# See the file "license.terms" for information on usage and redistribution
|
sl@0
|
8 |
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
sl@0
|
9 |
#
|
sl@0
|
10 |
# SCCS: @(#) httpd 1.2 98/02/20 14:51:59
|
sl@0
|
11 |
|
sl@0
|
12 |
#set httpLog 1
|
sl@0
|
13 |
|
sl@0
|
14 |
proc httpd_init {{port 8015}} {
|
sl@0
|
15 |
socket -server httpdAccept $port
|
sl@0
|
16 |
}
|
sl@0
|
17 |
proc httpd_log {args} {
|
sl@0
|
18 |
global httpLog
|
sl@0
|
19 |
if {[info exists httpLog] && $httpLog} {
|
sl@0
|
20 |
puts stderr "httpd: [join $args { }]"
|
sl@0
|
21 |
}
|
sl@0
|
22 |
}
|
sl@0
|
23 |
array set httpdErrors {
|
sl@0
|
24 |
204 {No Content}
|
sl@0
|
25 |
400 {Bad Request}
|
sl@0
|
26 |
401 {Authorization Required}
|
sl@0
|
27 |
404 {Not Found}
|
sl@0
|
28 |
503 {Service Unavailable}
|
sl@0
|
29 |
504 {Service Temporarily Unavailable}
|
sl@0
|
30 |
}
|
sl@0
|
31 |
|
sl@0
|
32 |
proc httpdError {sock code args} {
|
sl@0
|
33 |
global httpdErrors
|
sl@0
|
34 |
puts $sock "$code $httpdErrors($code)"
|
sl@0
|
35 |
httpd_log "error: [join $args { }]"
|
sl@0
|
36 |
}
|
sl@0
|
37 |
proc httpdAccept {newsock ipaddr port} {
|
sl@0
|
38 |
global httpd
|
sl@0
|
39 |
upvar #0 httpd$newsock data
|
sl@0
|
40 |
|
sl@0
|
41 |
fconfigure $newsock -blocking 0 -translation {auto crlf}
|
sl@0
|
42 |
httpd_log $newsock Connect $ipaddr $port
|
sl@0
|
43 |
set data(ipaddr) $ipaddr
|
sl@0
|
44 |
fileevent $newsock readable [list httpdRead $newsock]
|
sl@0
|
45 |
}
|
sl@0
|
46 |
|
sl@0
|
47 |
# read data from a client request
|
sl@0
|
48 |
|
sl@0
|
49 |
proc httpdRead { sock } {
|
sl@0
|
50 |
upvar #0 httpd$sock data
|
sl@0
|
51 |
|
sl@0
|
52 |
if {[eof $sock]} {
|
sl@0
|
53 |
set readCount -1
|
sl@0
|
54 |
} elseif {![info exists data(state)]} {
|
sl@0
|
55 |
|
sl@0
|
56 |
# Read the protocol line and parse out the URL and query
|
sl@0
|
57 |
|
sl@0
|
58 |
set readCount [gets $sock line]
|
sl@0
|
59 |
if [regexp {(POST|GET|HEAD) ([^?]+)\??([^ ]*) HTTP/(1.[01])} \
|
sl@0
|
60 |
$line x data(proto) data(url) data(query) data(httpversion)] {
|
sl@0
|
61 |
set data(state) mime
|
sl@0
|
62 |
httpd_log $sock Query $line
|
sl@0
|
63 |
} else {
|
sl@0
|
64 |
httpdError $sock 400
|
sl@0
|
65 |
httpd_log $sock Error "bad first line:$line"
|
sl@0
|
66 |
httpdSockDone $sock
|
sl@0
|
67 |
}
|
sl@0
|
68 |
return
|
sl@0
|
69 |
} elseif {$data(state) == "mime"} {
|
sl@0
|
70 |
|
sl@0
|
71 |
# Read the HTTP headers
|
sl@0
|
72 |
|
sl@0
|
73 |
set readCount [gets $sock line]
|
sl@0
|
74 |
} elseif {$data(state) == "query"} {
|
sl@0
|
75 |
|
sl@0
|
76 |
# Read the query data
|
sl@0
|
77 |
|
sl@0
|
78 |
if {![info exists data(length_orig)]} {
|
sl@0
|
79 |
set data(length_orig) $data(length)
|
sl@0
|
80 |
}
|
sl@0
|
81 |
set line [read $sock $data(length)]
|
sl@0
|
82 |
set readCount [string length $line]
|
sl@0
|
83 |
incr data(length) -$readCount
|
sl@0
|
84 |
}
|
sl@0
|
85 |
|
sl@0
|
86 |
# string compare $readCount 0 maps -1 to -1, 0 to 0, and > 0 to 1
|
sl@0
|
87 |
|
sl@0
|
88 |
set state [string compare $readCount 0],$data(state),$data(proto)
|
sl@0
|
89 |
httpd_log $sock $state
|
sl@0
|
90 |
switch -- $state {
|
sl@0
|
91 |
-1,mime,HEAD -
|
sl@0
|
92 |
-1,mime,GET -
|
sl@0
|
93 |
-1,mime,POST {
|
sl@0
|
94 |
# gets would block
|
sl@0
|
95 |
return
|
sl@0
|
96 |
}
|
sl@0
|
97 |
0,mime,HEAD -
|
sl@0
|
98 |
0,mime,GET -
|
sl@0
|
99 |
0,query,POST {
|
sl@0
|
100 |
# Empty line at end of headers,
|
sl@0
|
101 |
# or eof after query data
|
sl@0
|
102 |
httpdRespond $sock
|
sl@0
|
103 |
}
|
sl@0
|
104 |
0,mime,POST {
|
sl@0
|
105 |
# Empty line between headers and query data
|
sl@0
|
106 |
if {![info exists data(mime,content-length)]} {
|
sl@0
|
107 |
httpd_log $sock Error "No Content-Length for POST"
|
sl@0
|
108 |
httpdError $sock 400
|
sl@0
|
109 |
httpdSockDone $sock
|
sl@0
|
110 |
} else {
|
sl@0
|
111 |
set data(state) query
|
sl@0
|
112 |
set data(length) $data(mime,content-length)
|
sl@0
|
113 |
|
sl@0
|
114 |
# Special case to simulate servers that respond
|
sl@0
|
115 |
# without reading the post data.
|
sl@0
|
116 |
|
sl@0
|
117 |
if {[string match *droppost* $data(url)]} {
|
sl@0
|
118 |
fileevent $sock readable {}
|
sl@0
|
119 |
httpdRespond $sock
|
sl@0
|
120 |
}
|
sl@0
|
121 |
}
|
sl@0
|
122 |
}
|
sl@0
|
123 |
1,mime,HEAD -
|
sl@0
|
124 |
1,mime,POST -
|
sl@0
|
125 |
1,mime,GET {
|
sl@0
|
126 |
# A line of HTTP headers
|
sl@0
|
127 |
if {[regexp {([^:]+):[ ]*(.*)} $line dummy key value]} {
|
sl@0
|
128 |
set data(mime,[string tolower $key]) $value
|
sl@0
|
129 |
}
|
sl@0
|
130 |
}
|
sl@0
|
131 |
-1,query,POST {
|
sl@0
|
132 |
httpd_log $sock Error "unexpected eof on <$data(url)> request"
|
sl@0
|
133 |
httpdError $sock 400
|
sl@0
|
134 |
httpdSockDone $sock
|
sl@0
|
135 |
}
|
sl@0
|
136 |
1,query,POST {
|
sl@0
|
137 |
append data(query) $line
|
sl@0
|
138 |
if {$data(length) <= 0} {
|
sl@0
|
139 |
set data(length) $data(length_orig)
|
sl@0
|
140 |
httpdRespond $sock
|
sl@0
|
141 |
}
|
sl@0
|
142 |
}
|
sl@0
|
143 |
default {
|
sl@0
|
144 |
if {[eof $sock]} {
|
sl@0
|
145 |
httpd_log $sock Error "unexpected eof on <$data(url)> request"
|
sl@0
|
146 |
} else {
|
sl@0
|
147 |
httpd_log $sock Error "unhandled state <$state> fetching <$data(url)>"
|
sl@0
|
148 |
}
|
sl@0
|
149 |
httpdError $sock 404
|
sl@0
|
150 |
httpdSockDone $sock
|
sl@0
|
151 |
}
|
sl@0
|
152 |
}
|
sl@0
|
153 |
}
|
sl@0
|
154 |
proc httpdSockDone { sock } {
|
sl@0
|
155 |
upvar #0 httpd$sock data
|
sl@0
|
156 |
unset data
|
sl@0
|
157 |
catch {close $sock}
|
sl@0
|
158 |
}
|
sl@0
|
159 |
|
sl@0
|
160 |
# Respond to the query.
|
sl@0
|
161 |
|
sl@0
|
162 |
proc httpdRespond { sock } {
|
sl@0
|
163 |
global httpd bindata port
|
sl@0
|
164 |
upvar #0 httpd$sock data
|
sl@0
|
165 |
|
sl@0
|
166 |
switch -glob -- $data(url) {
|
sl@0
|
167 |
*binary* {
|
sl@0
|
168 |
set html "$bindata[info hostname]:$port$data(url)"
|
sl@0
|
169 |
set type application/octet-stream
|
sl@0
|
170 |
}
|
sl@0
|
171 |
*post* {
|
sl@0
|
172 |
set html "Got [string length $data(query)] bytes"
|
sl@0
|
173 |
set type text/plain
|
sl@0
|
174 |
}
|
sl@0
|
175 |
default {
|
sl@0
|
176 |
set type text/html
|
sl@0
|
177 |
|
sl@0
|
178 |
set html "<html><head><title>HTTP/1.0 TEST</title></head><body>
|
sl@0
|
179 |
<h1>Hello, World!</h1>
|
sl@0
|
180 |
<h2>$data(proto) $data(url)</h2>
|
sl@0
|
181 |
"
|
sl@0
|
182 |
if {[info exists data(query)] && [string length $data(query)]} {
|
sl@0
|
183 |
append html "<h2>Query</h2>\n<dl>\n"
|
sl@0
|
184 |
foreach {key value} [split $data(query) &=] {
|
sl@0
|
185 |
append html "<dt>$key<dd>$value\n"
|
sl@0
|
186 |
if {$key == "timeout"} {
|
sl@0
|
187 |
after $value ;# pause
|
sl@0
|
188 |
}
|
sl@0
|
189 |
}
|
sl@0
|
190 |
append html </dl>\n
|
sl@0
|
191 |
}
|
sl@0
|
192 |
append html </body></html>
|
sl@0
|
193 |
}
|
sl@0
|
194 |
}
|
sl@0
|
195 |
|
sl@0
|
196 |
# Catch errors from premature client closes
|
sl@0
|
197 |
|
sl@0
|
198 |
catch {
|
sl@0
|
199 |
if {$data(proto) == "HEAD"} {
|
sl@0
|
200 |
puts $sock "HTTP/1.0 200 OK"
|
sl@0
|
201 |
} else {
|
sl@0
|
202 |
puts $sock "HTTP/1.0 200 Data follows"
|
sl@0
|
203 |
}
|
sl@0
|
204 |
puts $sock "Date: [clock format [clock clicks]]"
|
sl@0
|
205 |
puts $sock "Content-Type: $type"
|
sl@0
|
206 |
puts $sock "Content-Length: [string length $html]"
|
sl@0
|
207 |
puts $sock ""
|
sl@0
|
208 |
flush $sock
|
sl@0
|
209 |
if {$data(proto) != "HEAD"} {
|
sl@0
|
210 |
fconfigure $sock -translation binary
|
sl@0
|
211 |
puts -nonewline $sock $html
|
sl@0
|
212 |
}
|
sl@0
|
213 |
}
|
sl@0
|
214 |
httpd_log $sock Done ""
|
sl@0
|
215 |
httpdSockDone $sock
|
sl@0
|
216 |
}
|
sl@0
|
217 |
|
sl@0
|
218 |
|