Extending Webserver.r
[1/8] from: rex:smallandmighty at: 12-Aug-2000 19:49
I'm trying to extend the Webserver.r script to handle POSTed data,
but I'm getting very confused as to what can and can't be done with ports.
How should the loop to fetch the POSTed data be constructed, i.e.
which commands should I be using to gather the data and what things
should I be testing to see when I've gathered all of the data sent by a
browser?
Is this sort of stuff covered in the Official Guide to Rebol?
:Eric
(Just a hapless graphics geek trying to learn a little networking)
[2/8] from: tim:johnsons-web at: 12-Aug-2000 16:35
Hi Eric:
I'm somewhat of a rebol newbie myself, but have done quite a bit
of CGI programming. I have only looked briefly at Webserver.r, but
I believe it is configured to handle http request specifically (that is, to
be a server). If you are not fairly well appraised of CGI issues, there are
a number of sites where you can get that information.
www.boutell.com is one example. Many others are out there. If you like
I can send you my cgiutil.r file, but even before that be certain that you
have a good grasp of CGI issues. When you request, I will send my
rebol "library" to you.
Tim
At 07:49 PM 8/12/00 -0400, you wrote:
[3/8] from: rex:smallandmighty at: 13-Aug-2000 1:05
On Sat, Aug 12, 2000, [tim--johnsons-web--com] <[tim--johnsons-web--com]> wrote:
>Hi Eric:
> I'm somewhat of a rebol newbie myself, but have done quite a bit
>of CGI programming. I have only looked briefly at Webserver.r, but
>I believe it is configured to handle http request specifically (that is, to
>be a server). If you are not fairly well appraised of CGI issues, there are
>a number of sites where you can get that information.
I should clarify things, I don't want to deal with CGI issues. All
I've been doing at work for the last 3 months is writing CGI scripts in
Rebol. It's gotten to the point where some of the things I'd like to do
would be best accomplished with a small custom HTTP server.
I think I solved my problem. I went digging around in the system/
schemes/http and looked at how it read in the http headers:
while [(line: pick port/sub-port 1) <> ""] [append headers join
line "^/"]
I put in a line like that, and then extracted the content-length. I
used the content-length in a read-io loop to get the form data. I have to
say that this is most assuredly non-intuitive. I would never have thought
to use pick like that.
I'd kill for a really good tutorial on the ins and outs of ports and
networking in Rebol.
>have a good grasp of CGI issues.
I do, that's why I'm biting the bullet and writing a web server. ;)
:Eric
[4/8] from: cybarite:sympatico:ca at: 13-Aug-2000 10:29
>>I do, that's why I'm biting the bullet and writing a web server. ;)
Eric,
Writing the web server might be good experience. These notes might
help (or not).
================================================================
I had a little bit of experience with this about a year ago after Carl
released the webserver.r
I wanted a rebol instance that would handle rebol scripts without
having to launch a new instance of rebol ala CGI approach.
This would give me faster response time in the mode that I was working in
then which
was the demonstration of some web prototypes. I wanted to be able to run
a webserver, the web applications, and all of the html pages and graphics
for a reasonable sized application prototype from a diskette. Hence REBOL.
I made a few changes to the webserver.r code which I think they are all in
the while block
with comments to indicate the goal. For me, these changes made sense 'cause
I was just trying to get the REBOL listener on port 80 to recognize that the
URL pointed
to a REBOL script - then knowing that, run it. So it this was fairly
intuitive.
====================================================================
while [on] [
http-port: first wait listen-port
request: first http-port
file: "index.html"
rebol: false ; turn this boolean off
mime: "text/plain"
parse request ["get" ["http" | "/ " | copy file to " "]]
parse file [thru "." [
"htm" (mime: "text/html") |
"html" (mime: "text/html") |
"gif" (mime: "image/gif") |
"jpg" (mime: "image/jpeg") |
"r" (rebol: true ; being asked to process a REBOL
script
mime: "text/html") ; assuming that want to emit html
from REBOL script
]
]
if not none? (parts: parse file "?") ; Look for GET arguments
[file: pick parts 1
; plug the GET arguments back into the query-string
; so that the REBOL script will be able to get them
system/options/cgi/query-string: pick parts 2]
print ["retrieving:" file]
any [
if not exists? web-dir/:file [send-error 404 file]
either rebol
; if the url points in the get points to a rebol script, then just
do it.
[data: copy ""
print: func [some_text] [append data some_text]
if error? try [do web-dir/:file] [send-error 400 file]
]
[if error? try [data: read/binary web-dir/:file] [send-error 400
file]]
send-page data mime
]
close http-port
]
============================================================================
=========
This did what I wanted and worked about an hour after I started it.
The howevers :
a. may not be what you want
b. only handles low usage
Cal Dixon ([deadzaphod--hotmail--com]) released a different version on the REBOL
List a few days later.
He updated it in January and February 2000.
Cal's would:
a. handle PUTs as well as GETs
b. handle concurrent access
but I did not run it since my few lines above met my needs.
I just checked the REBOL script site and could not find it.
The source that Cal produced was:
============================================================================
============
REBOL [
Title: "REBOL Web Server"
File: %webserv.r
Author: "Cal Dixon"
Email: [deadzaphod--hotmail--com]
Date: 26-Feb-2000
Purpose: { A Simple HTTP-Server that can run REBOL CGI scripts }
Notes:
{ 0.0.0.3: This version redirects all i/o to the web browser so 'read-io on
system/ports/input
can be used to get POSTed data, etc..
0.0.0.4: Now has better error checking and passes content-length as a
string like it should
0.0.0.5: Can now send multiple files at once
0.0.0.6: Now patches 'print and 'prin to work correctly and passes all
http headers to CGIs
also translates access to a folder to %index.html in that folder.
Also handles
the HTTP HEAD method in addition to GET and sends the
Last-Modified
header
0.0.0.7: Added logging in Extended Common Log Format - but for CGI scripts
the number of
bytes sent is recorded as 1, due to current limitations of this
program }
Version: 0.0.0.7
Category: 'web
]
wwwpath: %www/ ; change this to where the files are...
port: 80 ; change this to whatever port the server should
listen to
logfile: %webserv.log ; the name of the logfile or set to none
secure none
system/options/quiet: false
e: {<HTML><HEAD><TITLE>404 Not Found</TITLE></HEAD><BODY>Page not
found.</BODY></HTML>}
cgi-obj: make system/options/cgi [ context: func [] [ return 'context ] ]
listen: open/lines/direct join tcp://: port
inport: system/ports/input
outport: system/ports/output
queue: []
; these replacements for 'print and 'prin should work better for CGI scripts
prin: func [ out /local data ] [
data: replace/all (reform out) newline "^M^J"
write-io system/ports/output data length? data
return
]
print: func [ out /local data ] [
data: replace/all (reform out) newline "^M^J"
data: append data "^M^J"
write-io system/ports/output data length? data
return
]
quit: halt: func [] [throw]
www-send: func [ conn data ] [ write-io conn data length? data ]
either logfile [
write-log: func [ entry ] [ write/append logfile join to-string entry
newline ]
][
write-log: func [ ignorethisvalue ] []
]
get-http-headers: func [ conn /local line buffer a b c ] [
buffer: copy []
while [ ((line: first conn) <> "") and not none? line ] [
a: copy/part line b: find line ":"
c: trim next b
insert buffer reduce [ a c ]
]
return buffer
]
handle-cgi: func [ conn request query headers /local cd ] [
system/options/cgi: make cgi-obj compose [
server-software: "REBOL Web Server"
server-name: (read dns://)
gateway-interface: "CGI/1.1"
server-protocol: "HTTP/1.0"
server-port: "80"
query-string: (query)
request-method: (pick request 1)
script-name: (first parse (pick request 2) "?")
Content-Type: (select headers "Content-Type")
Content-Length: (select headers "Content-Length")
other-headers: (headers)
]
cd: what-dir
system/ports/output: conn
system/ports/input: conn
www-send conn "HTTP/1.0 200 OK^/"
if error? try [ catch [ do file-path ] ] []
system/ports/input: inport
system/ports/output: outport
change-dir cd
close conn
]
content-type?: func [ filename [string! file!] ] [
switch/default next find/last to-string filename "." [
"txt" [ return "text/plain" ]
"gif" [ return "image/gif" ]
"jpg" [ return "image/jpeg" ]
"png" [ return "image/png" ]
"mov" [ return "video/quicktime" ]
"tif" [ return "image/tiff" ]
"tiff" [ return "image/tiff" ]
"wav" [ return "audio/wav" ]
"xml" [ return "text/xml" ]
"xsl" [ return "text/xml" ]
"mid" [ return "audio/midi" ]
"r" [ return none ]
] [ return "text/html" ]
]
process-queue: func [ /local connection data file conn newqueue ] [
newqueue: copy []
foreach connection queue [
set [ conn file ] connection
data: copy/part file 2048
file: skip file 2048
write-io conn data length? data
either tail? file [
close conn
] [
insert/only newqueue reduce [ conn file ]
]
]
queue: newqueue
]
send-header: func [ conn result content-type data-length ] [
www-send conn rejoin [ "HTTP/1.0 " result newline "Content-Type: "
content-type newline
"Content-Length: " data-length newline "Date: " to-idate now newline
"Last-Modified: " to-idate modified? file-path "^/^/" ]
]
translate-request-to-resource: func [ file /local file-path ] [
if (last file) = #"/" [ append file "index.html" ]
file-path: clean-path join wwwpath to-file next file
if none? find file-path clean-path wwwpath [
file-path: clean-path join wwwpath "index.html"
]
if dir? file-path [ append file-path "/index.html" ]
return file-path
]
http-log: func [ host request status bytes /extended headers /local when
agent referer] [
when: rejoin [ replace/all copy/part mold now 11 "-" "/" replace skip
mold now 11 "/" ":" ]
replace when "-" " -"
either (agent: select headers "User-Agent") [
agent: join {"} [ agent {"} ]
][
agent: "-"
]
either (referer: select headers "Referer") [
referer: join {"} [ referer {"} ]
][
referer: "-"
]
reform [
host
"- -"
rejoin [ "[" when "]" ]
mold form request
status
bytes
either extended [
reform [ referer agent ]
][ "" ]
]
]
handle-new-connections: func [ /local data conn http-headers ] [
if none? wait reduce [ listen 0 ] [ return ]
request: parse first (conn: first listen) none
if (length? queue) > 3000 [
insert conn "HTTP/1.0 503 Server Overloaded^/"
close conn return
] ; refuse connections if server is overloaded
request-method: pick request 1
set [ file urlquery ] parse (pick request 2) "?"
file-path: translate-request-to-resource file
http-headers: get-http-headers conn
either exists? file-path [
either none? content: content-type? file-path [
write-log http-log/extended conn/host request 200 1 http-headers
handle-cgi conn request urlquery http-headers
return
] [
write-log http-log/extended conn/host request 200 size? file-path
http-headers
set [ responce data ] reduce [ "200 OK" (data: read/binary
file-path) ]
]
] [
write-log http-log/extended conn/host request 404 0 http-headers
set [ responce content data file-path ] reduce [ "404 Not Found"
text/html
e %. ]
]
send-header conn responce content length? data
if request-method = "HEAD" [ close conn return ]
insert/only queue reduce [ conn data ]
]
forever [
if ( zero? ( length? queue ) ) [ wait listen ]
handle-new-connections
process-queue
]
[5/8] from: tim:johnsons-web at: 13-Aug-2000 8:22
Hello
At 01:05 AM 8/13/00 -0400, you wrote:
> while [(line: pick port/sub-port 1) <> ""] [append headers join
>line "^/"]
>say that this is most assuredly non-intuitive. I would never have thought
>to use pick like that.
What's "intuitive" in C, might seem "non-intuitive" in rebol. And vice versa.
Even tho' C remains my "first" language for the time being, rebol will probably
eventually take C's place and part of the reason is rebol's sheer productivity
and it's closer resemblance to human language. I' m reading Elan's book.
and as I progress through it, all rebol syntax is becoming more intuitive.
> I'd kill for a really good tutorial on
> the ins and outs of ports and
>networking in Rebol.
Amen to that!
Am writing a server using my current legacy of C/C++ resources. I will then
probably use rebol scripts as an interface.
I would think that the more I learn of ports and networking, and the more I
learn of rebol, then the use of ports and networking in rebol will become
clearer.
> >have a good grasp of CGI issues.
>
> I do, that's why I'm biting the bullet
> and writing a web server. ;)
>
>:Eric
regards
Tim
[6/8] from: rex::smallandmighty::com at: 13-Aug-2000 21:24
On Sun, Aug 13, 2000, [cybarite--sympatico--ca] <[cybarite--sympatico--ca]> wrote:
>>>I do, that's why I'm biting the bullet and writing a web server. ;)
>
>Eric,
>
>Writing the web server might be good experience. These notes might
>help (or not).
Thanks, lots of good code in there. My next set of questions was
going to revolve around handling multiple connections.
:Eric
[7/8] from: mdb:gci at: 14-Aug-2000 16:21
Hello,
Cal Dixon's Web server is out at www.rebol.org, in the script library under
the Web section.
However i downloaded it last week, and wasn't able to get it to work,
either with an .html file or .cgi file, running REBOL/CORE 2.3 under
Windows 95.
If anyone has it working, or downloads it and gets it to work, maybe they
could enlighten me.
Thanks.
Mike.
[8/8] from: deadzaphod::hotmail at: 14-Aug-2000 22:01
It worked under 2.2, but I haven't run it since upgrading. I suspect the problem is
due to the "wait 0" bug in /Core 2.3.
When I'm back on my real computer I'll take a look and see if this can be fixed easily...
- Cal Dixon ([deadzaphod--hotmail--com])
-><-
[mdb--gci--net] wrote on 8/14/00 5:30 pm: