2 simple HTTP server and class browser
3 Copyright (C) 199? <unknown author>
4 Changes by Ketmar // Vampire Avalon
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 "******************************************************
26 String class additions. These help parsing and
27 handling URLs and HTML.
28 ******************************************************"
33 (self includes: '<>&') ifFalse: [ ^self ].
34 res := StringBuffer new.
36 c == $< ifTrue: [ res << '<' ]
38 c == $> ifTrue: [ res << '>' ]
40 c == $& ifTrue: [ res << '&' ]
41 ifFalse: [ res << c ]]]].
47 "****************************************"
49 "****************************************"
50 Object subclass: HTTPRequest [
51 | sock reqPath reqAction reqArgs reqRawData reqPathAndArgs reqError reqLength |
55 ^(self rawData size > 0)
59 | responseSize tmpResponse |
60 tmpResponse := StringBuffer new.
61 "get the response size"
62 responseSize := aResp size.
65 'HTTP/1.0 200 LittleSmalltalk\r\n' <<
66 'Content-Type: text/html\r\n' <<
67 ('Content-Length: ' + (responseSize printString) + '\r\n') <<
69 "add the response text"
71 sock send: tmpResponse asString.
72 "close the connection now"
78 | i tempData contentLength |
79 "read the request raw data; this does some parsing"
80 reqRawData isNil ifFalse: [ ^reqRawData ].
81 "is the socket nil? or not open?"
82 sock isNil ifTrue: [ ^nil ].
83 " get the data from the socket until we see the header/body delimiter"
84 tempData := sock recv: 1.
85 [ (tempData position: '\r\n\r\n') isNil]
86 whileTrue: [ tempData := tempData + (sock recv: 1) ].
87 "OK, we have all the headers, what kind of request is it?"
88 reqRawData := tempData.
89 reqLength := tempData size.
90 "if this is a POST, we need to get the length and read the data"
91 ((self action) = 'POST') ifTrue: [
92 'Processing POST action.' printNl.
93 i := tempData position: 'Content-Length:' .
94 i isNil ifTrue: [ reqError := '400 POST without Content-Length header'. ^nil ].
95 "find the first digit character"
96 i := i + ('Content-Length:' size).
97 [ (tempData at: i) isBlank ] whileTrue: [ i := i+1 ].
99 "convert the size into an integer while reading it in"
100 [ (tempData at: i) isDigit ] whileTrue:
101 [ contentLength := (contentLength * 10) + (((tempData at: i) value) - ($0 value)). i := i+1 ].
102 "store the length for later"
103 reqLength := contentLength.
104 "the total length is the length of the header plus separator plus body, -1 for zero start"
105 contentLength := contentLength + (tempData position: '\r\n\r\n') + 3.
106 "read until we have all the data"
107 [ (tempData size) < contentLength ] whileTrue: [tempData := tempData + (sock recv: 1)].
109 ifFalse: [ reqLength := 0 ].
110 "we have all the raw data. We've set reqAction, reqLength already, so set reqRawData"
111 reqRawData := tempData.
116 | lines firstLine fields |
117 reqPathAndArgs isNil ifFalse: [ ^reqPathAndArgs ].
118 "break raw data into lines"
119 lines := (self rawData) break: '\r\n'.
120 firstLine := lines first.
122 fields := firstLine break: ' '.
123 "path plus arguments is second field"
125 reqPathAndArgs := fields first.
130 "if it was set once, return it"
131 reqAction isNil ifFalse: [ ^reqAction.].
132 'reqAction before compileWithClass: ' print.
133 reqAction printString printNl.
134 ((self rawData) position: 'GET') = 1 ifTrue: [ reqAction := 'GET'. ].
135 ((self rawData) position: 'POST') = 1 ifTrue: [ reqAction := 'POST'. ].
136 reqAction isNil ifTrue: [ reqAction := 'UNKNOWN' ].
138 reqAction printString printNl.
144 reqPath isNil ifFalse: [ ^reqPath ].
145 reqPath = '' ifTrue: [ ^nil ].
146 pathArgField := self pathAndArgs.
147 pathArgField isNil ifTrue: [ reqPath := ''. ^nil ].
148 i := pathArgField position: '?'.
149 i isNil ifTrue: [ reqPath := pathArgField. ^reqPath ].
150 reqPath := pathArgField from: 1 to: (i - 1).
155 | i pathArgField argsData keyValList key val argList |
156 "get args for both URL and POST data"
157 reqArgs isNil ifFalse: [ ^reqArgs ].
158 reqArgs := Dictionary new.
160 pathArgField := self pathAndArgs.
161 (pathArgField isNil) ifFalse: [
162 i := pathArgField position: '?'.
165 argsData := pathArgField from: (i+1) to: (pathArgField size).
166 "append a & to make sure that we break correctly"
167 argsData := argsData + '&'.
170 "copy data from the form data if this is a POST"
171 (self action) = 'POST' ifTrue: [
172 i := ((self rawData size) + 1) - reqLength.
173 argsData := argsData + ((self rawData) from: i to: (self rawData size))
175 "do a little error checking"
176 argsData isNil ifTrue: [ ^reqArgs ].
177 (argsData size) = 0 ifTrue: [ ^reqArgs ].
178 "split up the key value pairs"
179 keyValList := argsData break: '&'.
180 keyValList do: [ :keyValField |
181 argList := keyValField break: '='.
182 key := argList first.
184 " handle case where key indicates a flag "
186 ifTrue: [ val := true asString ]
187 ifFalse: [ val := argList first asString ].
188 val isNil ifTrue: [ val := 'no value' ].
189 reqArgs at: (key fromUrl asSymbol) put: (val fromUrl).
195 ^(self args) at: aSymbol ifAbsent: [ nil ].
200 "*******************************"
201 " HTTPDispatcher class "
202 "*******************************"
203 Object subclass: HTTPDispatcher [
204 | map env runFlag sock request errorHandler |
206 register: aBlock at: aPath [
207 map isNil ifTrue: [ map := Dictionary new ].
208 map at: aPath put: aBlock.
212 registerErrorHandler: anObj [
213 errorHandler := anObj.
218 | tmpRequest aBlock clientSock |
220 env := Dictionary new.
221 aSock listen < 0 ifTrue: [
223 self error: 'can''t listen'
225 [ runFlag = true ] whileTrue: [
226 "get a request from the socket and dispatch it"
227 clientSock := aSock accept.
228 tmpRequest := HTTPRequest new.
229 (tmpRequest read: clientSock) ifTrue: [
230 aBlock := map at: (tmpRequest path) ifAbsent: [ nil ].
232 ifTrue: [ errorHandler value: tmpRequest value: env]
233 ifFalse: [ aBlock value: tmpRequest value: env ].
245 "*********************************"
246 " HTTPClassBrowser class "
247 "*********************************"
248 Object subclass: HTTPClassBrowser [
250 listPackagesOn: aReq [
252 outBuf := StringBuffer new.
253 outBuf << '<HTML><BODY>'.
254 Package packages keysDo: [ :obj |
257 '<A HREF="/class_list_frame?package=' <<
259 '" target="class_list_frame">' <<
263 outBuf << '</BODY></HTML>'.
264 ^aReq response: outBuf
267 addClassName: outBuf class: obj [
269 obj isMeta ifFalse: [
270 name := obj printString.
272 '<A HREF="/method_list_frame?class=' <<
274 '" target="method_list_frame">' <<
280 listClassesOn: aReq [
282 pkg := aReq at: #package.
283 pkg ifNotNil: [ pkg := Package find: pkg ].
284 outBuf := StringBuffer new.
285 outBuf << '<HTML><BODY>'.
288 globals do: [:obj | (obj isKindOf: Class) ifTrue: [ self addClassName: outBuf class: obj ]].
290 pkg classes do: [:obj | (obj isKindOf: Class) ifTrue: [ self addClassName: outBuf class: obj ]].
292 outBuf << '</BODY></HTML>'.
293 ^aReq response: outBuf
296 addMethodToBuf: outBuf class: classStr name: name asMeta: asMeta [
299 asMeta ifTrue: [ n := '^' + n ].
301 '<A HREF="/edit_frame?class=' <<
305 '" target="edit_frame">' <<
310 addClassMethodsToBuf: outBuf class: class classStr: classStr asMeta: asMeta [
311 (class methods size) = 0 ifTrue: [ ^false ].
312 class methods keysAndValuesDo: [ :name :meth |
313 "HTML doesn't like < signs"
314 self addMethodToBuf: outBuf class: classStr name: name asMeta: asMeta.
319 listMethodsOn: aReq [
320 | outBuf classStr class cc |
321 outBuf := StringBuffer new.
323 outBuf << '<HTML><BODY>'.
324 classStr := aReq at: #class.
325 "if there isn't a class string chosen"
326 classStr isNil ifTrue: [ outBuf << '<B>No class chosen.</B></BODY></HTML>'. ^aReq response: outBuf ].
327 class := globals at: (classStr asSymbol) ifAbsent: [ nil ].
328 class isNil ifTrue: [ outBuf << '<B>No such class!</B></BODY></HTML>'. ^aReq response: outBuf ].
329 "some classes have no methods"
330 (cc := class class) isMeta ifTrue: [
331 self addClassMethodsToBuf: outBuf class: cc classStr: classStr asMeta: true.
332 (cc methods size) = 0 ifFalse: [
333 (class methods size) = 0 ifFalse: [ outBuf << '<hr>' ]
336 self addClassMethodsToBuf: outBuf class: class classStr: classStr asMeta: false.
337 "outBuf << '<B>No methods in class</B>'"
338 outBuf << '</BODY></HTML>'.
339 ^aReq response: outBuf.
342 frameSubclass: aReq [
343 | outBuf classStr newClassStr |
344 outBuf := StringBuffer new.
345 outBuf << '<html><body>'.
346 aReq action = 'POST' ifFalse: [
347 outBuf << '<b>POST form submission required.</b>'
349 classStr := aReq at: #class.
350 newClassStr := aReq at: #subclassname.
353 (globals at: (classStr asSymbol) ifAbsent: [ nil ]) ifNil: [
354 outBuf << '<b>ERROR: can''t subclass non-existing class.</b>'
356 aReq at: #class put: newClassStr.
357 ^self listMethodsOn: aReq.
360 outBuf << '</body></html>'.
361 ^aReq response: outBuf.
365 | outBuf classStr class methStr method body |
366 outBuf := StringBuffer new.
367 outBuf << '<HTML><BODY>'.
368 classStr := aReq at: #class.
369 "if there isn't a class string chosen"
370 classStr isNil ifTrue: [ outBuf << '<B>No class chosen.</B></BODY></HTML>'. ^aReq response: outBuf ].
371 class := globals at: (classStr asSymbol) ifAbsent: [ nil ].
372 class isNil ifTrue: [ outBuf << '<B>No such class!</B></BODY></HTML>'. ^aReq response: outBuf ].
373 "if there isn't a method string chosen"
374 methStr := aReq at: #method.
376 "debugging (aReq args) keysAndValuesDo: [ :key :val | outBuf << ((key printString) + ' = ' + (val printString) + '<BR>') ]."
377 methStr = 'no value' ifTrue: [ outBuf + '<B>No Value!</B></BODY></HTML>'. ^aReq response: outBuf ].
378 ((methStr isNil) or: [ methStr isEmpty ]) ifTrue: [ outBuf << '<B>No method chosen.</B></BODY></HTML>'. ^aReq response: outBuf ].
379 methStr firstChar == $^
381 method := (class class methods) at: ((methStr from: 2) asSymbol) ifAbsent: [ nil ].
383 method := (class methods) at: (methStr asSymbol) ifAbsent: [ nil ].
385 method isNil ifTrue: [ outBuf << '<B>No such method!</B></BODY></HTML>'. ^aReq response: outBuf ].
386 (body := method text) ifNil: [ body := '"no source"' ].
387 methStr firstChar == $^ ifTrue: [ body := '^' + body ].
389 '<FORM ACTION="/compile_method?class=' <<
392 (method name printString toUrl) <<
393 '" ENCTYPE="application/x-www-form-urlencoded" METHOD="POST">' <<
394 '<TEXTAREA COLS=60 ROWS=40 NAME="methsrc" WRAP="OFF">' <<
397 '<BR><INPUT TYPE=SUBMIT NAME=compile VALUE="Compile">' <<
398 '<br><br><input type="submit" name=dogst value="Execute GST-style code">' <<
401 ^aReq response: outBuf.
404 compileMethodOn: aReq [
405 | outBuf classStr class methSrc action meth p warnings |
406 outBuf := StringBuffer new.
407 outBuf << '<HTML><BODY>'.
408 "check to make sure this is a POST"
409 action := aReq action.
410 action = 'POST' ifFalse: [
411 outBuf << '<B>POST form submission required.</B></BODY></HTML> '.
412 ^aReq response: outBuf.
414 methSrc := aReq at: #methsrc.
415 (aReq at: #compile) ~= 'Compile' ifTrue: [
416 methSrc isNil ifTrue: [ outBuf << '<B>No source!</B></BODY></HTML>'. ^aReq response: outBuf ].
417 "filter out carriage returns"
418 methSrc := (methSrc printString) reject: [ :c | c isCR ].
419 methSrc := StringStream newWith: (methSrc removeTrailingBlanks).
421 p := GSTParser newWith: methSrc.
422 p errorBlock: [ :msg |
423 outBuf << '<b>COMPILATION ERROR: ' << (msg htmlEscape) << '</b>'.
424 outBuf << '</body></html>'.
425 ^aReq response: outBuf.
427 p warningBlock: [ :msg |
428 outBuf << '<b>COMPILATION WARNING: ' << (msg htmlEscape) << '</b><br />'.
431 outBuf << warnings << '</body></html>'.
432 ^aReq response: outBuf.
434 "if there isn't a class string chosen"
435 classStr := aReq at: #class.
436 classStr isNil ifTrue: [ outBuf << '<B>No class chosen.</B></BODY></HTML>'. ^aReq response: outBuf ].
437 class := globals at: (classStr asSymbol) ifAbsent: [ nil ].
438 class isNil ifTrue: [ outBuf << '<B>No such class!</B></BODY></HTML>'. ^aReq response: outBuf ].
439 "get the method source."
440 methSrc isNil ifTrue: [ outBuf << '<B>No method source!</B></BODY></HTML>'. ^aReq response: outBuf ].
441 methSrc isEmpty ifTrue: [ outBuf << '<B>No method source!</B></BODY></HTML>'. ^aReq response: outBuf ].
442 "compile and add method"
443 p := LstCompiler new.
444 p errorBlock: [ :msg :lineNum |
445 outBuf << '<b>COMPILATION ERROR: near line ' << (lineNum asString) << ': ' << (msg htmlEscape) << '</b>'.
446 outBuf << '</body></html>'.
447 ^aReq response: outBuf.
449 p warningBlock: [ :msg :lineNum |
450 outBuf << '<b>COMPILATION WARNING: near line' << (lineNum asString) << ': ' << (msg htmlEscape) << '</b><br />'.
452 (meth := (class addMethod: methSrc withCompiler: p)) ifNotNil: [
453 outBuf << (meth name printString) << ' added.'.
455 outBuf << '</body></html>'.
456 ^aReq response: outBuf.
459 showBaseFrameOn: aReq [
461 outBuf := StringBuffer new.
464 '<HTML><FRAMESET COLS="50%,50%" FRAMEBORDER="YES">' <<
465 '<FRAME SRC="/control_list_frame" NAME="control_list_frame">' <<
466 '<FRAME SRC="/edit_frame" NAME="edit_frame">' <<
467 '</FRAMESET></HTML>'.
469 ^aReq response: outBuf.
472 showControlListFrameOn: aReq [
474 outBuf := StringBuffer new.
476 outBuf << '<HTML><FRAMESET ROWS="80%,20%" FRAMEBORDER="YES">' <<
477 '<FRAME SRC="/list_frame" NAME="list_frame">' <<
478 '<FRAME SRC="/control_frame" NAME="control_frame">' <<
479 '</FRAMESET></HTML>'.
481 ^aReq response: outBuf.
484 showListFrameOn: aReq [
486 outBuf := StringBuffer new.
488 outBuf << '<HTML><FRAMESET COLS="20%,40%,40%" FRAMEBORDER="YES">' <<
489 '<FRAME SRC="/package_list_frame" NAME="package_list_frame">' <<
490 '<FRAME SRC="/class_list_frame" NAME="class_list_frame">' <<
491 '<FRAME SRC="/method_list_frame" NAME="method_list_frame">' <<
492 '</FRAMESET></HTML>'.
494 ^aReq response: outBuf.
497 showControlFrameOn: aReq [
499 outBuf := StringBuffer new.
501 outBuf addLast: '<HTML><BODY><FORM METHOD="GET" ACTION="/stop" TARGET="_top">'.
502 outBuf addLast: '<INPUT TYPE=SUBMIT NAME=stop VALUE="Stop Browser">'.
503 outBuf addLast: '</FORM></BODY></HTML>'.
505 ^aReq response: outBuf.
510 outBuf := StringBuffer new.
512 outBuf addLast: '<HTML><BODY><B>Path not recognized!</B><BR>'.
513 outBuf addLast: '<PRE>'.
514 outBuf addLast: ('path: ' + (aReq path) + '\n').
515 aReq args isNil ifFalse: [ (aReq args) keysAndValuesDo:
516 [ :key :val | outBuf addLast: ((key printString) + '=' + (val printString) + '\n') ] ].
518 outBuf addLast: '</PRE></BODY></HTML>'.
520 ^aReq response: outBuf.
525 dispatcher := HTTPDispatcher new.
527 dispatcher register: [:aReq :anEnv | self showBaseFrameOn: aReq. nil]
529 dispatcher register: [:aReq :anEnv | self showControlListFrameOn: aReq. nil]
530 at: '/control_list_frame'.
531 dispatcher register: [:aReq :anEnv | self showListFrameOn: aReq. nil]
533 dispatcher register: [:aReq :anEnv | self showControlFrameOn: aReq. nil]
534 at: '/control_frame'.
535 dispatcher register: [:aReq :anEnv | self listPackagesOn: aReq. nil]
536 at: '/package_list_frame'.
537 dispatcher register: [:aReq :anEnv | self listClassesOn: aReq. nil]
538 at: '/class_list_frame'.
539 dispatcher register: [:aReq :anEnv | self listMethodsOn: aReq. nil]
540 at: '/method_list_frame'.
541 dispatcher register: [:aReq :anEnv | self editMethodOn: aReq. nil]
543 dispatcher register: [:aReq :anEnv | self compileMethodOn: aReq. nil]
544 at: '/compile_method'.
545 dispatcher register: [:aReq :anEnv | self frameSubclass: aReq. nil]
547 dispatcher register: [:aReq :anEnv | aReq response: '<HTML><BODY><B>Class browser stopped.</B></BODY></HTML>'. dispatcher stop. aSock close. nil]
550 dispatcher registerErrorHandler: [ :aReq :anEnv | self showErrorOn: aReq. nil].
551 dispatcher startOn: aSock.
557 "create a default socket on which to listen"
559 [ cnt > 0 ] whileTrue: [
560 sock := TCPSocket new.
561 (sock bind: '127.0.0.1' port: 6789) < 0 ifFalse: [
562 'starting...' printNl.
567 'binding failed.' printNl.
571 self error: 'can''t start browser: binding error'.
576 { HTTPClassBrowser start }