summaryrefslogtreecommitdiffstats
path: root/tcllib/examples/httpd
diff options
context:
space:
mode:
authorWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
committerWilliam Joye <wjoye@cfa.harvard.edu>2016-10-27 19:39:39 (GMT)
commitea28451286d3ea4a772fa174483f9a7a66bb1ab3 (patch)
tree6ee9d8a7848333a7ceeee3b13d492e40225f8b86 /tcllib/examples/httpd
parentb5ca09bae0d6a1edce939eea03594dd56383f2c8 (diff)
parent7c621da28f07e449ad90c387344f07a453927569 (diff)
downloadblt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.zip
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.gz
blt-ea28451286d3ea4a772fa174483f9a7a66bb1ab3.tar.bz2
Merge commit '7c621da28f07e449ad90c387344f07a453927569' as 'tcllib'
Diffstat (limited to 'tcllib/examples/httpd')
-rw-r--r--tcllib/examples/httpd/htdocs/content.file.md55
-rw-r--r--tcllib/examples/httpd/htdocs/content.form.md0
-rw-r--r--tcllib/examples/httpd/htdocs/content.md14
-rw-r--r--tcllib/examples/httpd/htdocs/content.proxy.md20
-rw-r--r--tcllib/examples/httpd/htdocs/content.scgi.md20
-rw-r--r--tcllib/examples/httpd/htdocs/content.server.md42
-rw-r--r--tcllib/examples/httpd/htdocs/example.md157
-rw-r--r--tcllib/examples/httpd/htdocs/index.md22
-rw-r--r--tcllib/examples/httpd/htdocs/operations.md30
-rw-r--r--tcllib/examples/httpd/htdocs/plume.pngbin0 -> 6265 bytes
-rw-r--r--tcllib/examples/httpd/htdocs/reply.md16
-rw-r--r--tcllib/examples/httpd/htdocs/server.md0
-rw-r--r--tcllib/examples/httpd/httpd.tcl86
13 files changed, 462 insertions, 0 deletions
diff --git a/tcllib/examples/httpd/htdocs/content.file.md b/tcllib/examples/httpd/htdocs/content.file.md
new file mode 100644
index 0000000..b2c1b3a
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.file.md
@@ -0,0 +1,55 @@
+httpd::content::file
+====================
+Back to: [Index](index.md) | [Package httpd::content](content.md)
+
+The **httpd::content::file** class implements a system for sharing a
+local file structure via http.
+
+## Special file handling
+
+### Directories
+
+When a directory path is requested, the system searches for one of the following (in order):
+
+* index.tml
+* index.md
+* index.html
+
+If one of these files is found, it is delivered as the response to the request. If no index file
+was found, the object will call the object's *DirectoryListing* method to
+deliver a dynamic listing of the files in the directory.
+
+### .md files
+
+Files with the .md extension are parsed using the *Markdown* package.
+
+### .tml files
+
+Files with the .tml extension are parsed using a call to *subst*.
+This allows them to deliver content in the same manner as tclhttpd. The
+contents of the *query_headers* are loaded as local variables prior to
+the *subst* call. NOTE: Unlike Tclhttpd, the substitution is performed
+inside of the reply object's namespace, not the local interpreter. Thus,
+template files can exercise the object's methods using the "my" command.
+
+## Dispatch Parameters
+
+Objects of this class needs additional information from the server in
+order to operate. These fields should be coded into the **add_root** call.
+
+### path *filepath*
+
+The **path** parameter specifies the root of the file path to be exposed.
+
+## Methods
+
+### DirectoryListing *local_file*
+
+Generates an HTML listing of a file path. The default implementation is a *very*
+rudimentary **glob --nocomplain [file join $local_path \*]**
+
+### FileName
+
+Converts the **REQUEST_URI** from query_headers into a local file path. This
+method searches first for the file name verbatim. If not found, it then searches
+for the same file name with a .md, .html, or .tml extension (in that order.)
diff --git a/tcllib/examples/httpd/htdocs/content.form.md b/tcllib/examples/httpd/htdocs/content.form.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.form.md
diff --git a/tcllib/examples/httpd/htdocs/content.md b/tcllib/examples/httpd/htdocs/content.md
new file mode 100644
index 0000000..abde776
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.md
@@ -0,0 +1,14 @@
+http::content
+=============
+[Back to Index](index.md)
+
+The **httpd::content** package is an extension to the core **httpd** package
+which provides all of the bread and butter functions needed to implement a
+basic webserver.
+
+* [Content Server](content.server.md)
+* [Form handler](content.form.md)
+* [File handler](content.file.md)
+* [SCGI handler](content.scgi.md)
+* [Proxy handler](content.proxy.md)
+ \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/content.proxy.md b/tcllib/examples/httpd/htdocs/content.proxy.md
new file mode 100644
index 0000000..32f757b
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.proxy.md
@@ -0,0 +1,20 @@
+httpd::content::proxy
+=============
+Back to: [Index](index.md) | [Package httpd::content](content.md)
+
+The proxy handler farms out the generation of content to an external process running
+on a known port. The external process is assumed to be a proxy server, and it is the job
+of this object to transform the query as received into a form that is understood by
+the external server.
+
+To implement a proxy handler, replace **proxy_info** with one that will return a list
+containing the following:
+
+ PROXYHOST PROXYPORT PROXYURI
+
+* PROXYHOST - The hostname or IP address of the server running the process
+* PROXYPORT - The port to connect to
+* PROXYURI - The replacement GET/POST/etc request to make to the external process.
+
+The **proxy_info** method also makes a handly place to spawn a locally hosted process on demand.
+For an example of this, see the [docserver.tcl](docserver.tcl) Example. \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/content.scgi.md b/tcllib/examples/httpd/htdocs/content.scgi.md
new file mode 100644
index 0000000..07b7f11
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.scgi.md
@@ -0,0 +1,20 @@
+httpd::content::scgi
+=============
+Back to: [Index](index.md) | [Package httpd::content](content.md)
+
+The SCGI handler farms out the generation of content to an external process
+running at a known port. Because this process is persistent, the SCGI system
+avoids the overhead of spawning and spooling up an external process with
+every page view.
+
+To implement an SCGI handler, replace the **scgi_info** method with one
+that will return a list containing the following:
+
+ SCGIHOST SCGIPORT SCGISCRIPT
+
+* SCGIHOST - The hostname or IP address of the server running the process
+* SCGIPORT - The port to connect to
+* SCGISCRIPT - The SCGISCRIPT parameter which will be passed to the external process via headers.
+
+The **scgi_info** method also makes a handly place to spawn a locally hosted process on demand.
+For an example of this, see the [docserver.tcl](docserver.tcl) Example. \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/content.server.md b/tcllib/examples/httpd/htdocs/content.server.md
new file mode 100644
index 0000000..fae985e
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/content.server.md
@@ -0,0 +1,42 @@
+http::content
+=============
+Back to: [Index](index.md) | [Package httpd::content](content.md)
+
+
+## Class: httpd::server::dispatch
+
+The **httpd::server::dispatch** adds additional functionality to the basic
+**httpd::server** class. It's *dispatch* method performs a pattern search
+based on url's registered via the *add_uri* method. That *add_uri* method
+allows the developer to specify which class will handle replies, as well as
+pass configuration information onto those objects.
+
+### Option doc_root
+
+Specifiying a *doc_root* will introduce a pattern search of last resort to
+find a matching URI as a file subordinate to the *doc_root*. Also, if the
+*doc_root* is specified, the system will search the root folder for the following
+templates:
+
+* notfound.tml - A site specific "404 File not found" template
+* internal_error.tml - A site specific "505 Internal Server Error" template
+
+### Method add_uri *pattern* *info*
+
+*add_uri* appends a new pattern to the server's internal pattern search dict.
+Patterns utilize **string match**, so any global characters or patterns for
+string match will work.
+
+Patterns are matched in the order in which they were given. In the example:
+
+<pre><code>SERVER add_uri /home* {...}
+SERVER add_uri /home/star/runner* {...}</code></pre>
+
+The pattern for /home/star/runner* will never be reached because /home* was specified first.
+
+The **info** argument contains a dict that will be passed by the *connect* method of the
+server to the *dispatch* method of the reply. Only two fields are reserved by the core of
+httpd:
+
+* class - The base class for the reply
+* mixin - The class to be mixed into the new object immediately prior to invoking the object's *dispatch* method.
diff --git a/tcllib/examples/httpd/htdocs/example.md b/tcllib/examples/httpd/htdocs/example.md
new file mode 100644
index 0000000..aa854b8
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/example.md
@@ -0,0 +1,157 @@
+An h1 header
+============
+
+Paragraphs are separated by a blank line.
+
+2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
+look like:
+
+ * this one
+ * that one
+ * the other one
+
+Note that --- not considering the asterisk --- the actual text
+content starts at 4-columns in.
+
+> Block quotes are
+> written like so.
+>
+> They can span multiple paragraphs,
+> if you like.
+
+Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
+in chapters 12--14"). Three dots ... will be converted to an ellipsis.
+Unicode is supported. ☺
+
+
+
+An h2 header
+------------
+
+Here's a numbered list:
+
+ 1. first item
+ 2. second item
+ 3. third item
+
+Note again how the actual text starts at 4 columns in (4 characters
+from the left side). Here's a code sample:
+
+ # Let me re-iterate ...
+ for i in 1 .. 10 { do-something(i) }
+
+As you probably guessed, indented 4 spaces. By the way, instead of
+indenting the block, you can use delimited blocks, if you like:
+
+~~~
+define foobar() {
+ print "Welcome to flavor country!";
+}
+~~~
+
+(which makes copying & pasting easier). You can optionally mark the
+delimited block for Pandoc to syntax highlight it:
+
+~~~python
+import time
+# Quick, count to ten!
+for i in range(10):
+ # (but not *too* quick)
+ time.sleep(0.5)
+ print i
+~~~
+
+
+
+### An h3 header ###
+
+Now a nested list:
+
+ 1. First, get these ingredients:
+
+ * carrots
+ * celery
+ * lentils
+
+ 2. Boil some water.
+
+ 3. Dump everything in the pot and follow
+ this algorithm:
+
+ find wooden spoon
+ uncover pot
+ stir
+ cover pot
+ balance wooden spoon precariously on pot handle
+ wait 10 minutes
+ goto first step (or shut off burner when done)
+
+ Do not bump wooden spoon or it will fall.
+
+Notice again how text always lines up on 4-space indents (including
+that last line which continues item 3 above).
+
+Here's a link to [a website](http://foo.bar), to a [local
+doc](local-doc.html), and to a [section heading in the current
+doc](#an-h2-header). Here's a footnote [^1].
+
+[^1]: Footnote text goes here.
+
+Tables can look like this:
+
+size material color
+---- ------------ ------------
+9 leather brown
+10 hemp canvas natural
+11 glass transparent
+
+Table: Shoes, their sizes, and what they're made of
+
+(The above is the caption for the table.) Pandoc also supports
+multi-line tables:
+
+-------- -----------------------
+keyword text
+-------- -----------------------
+red Sunsets, apples, and
+ other red or reddish
+ things.
+
+green Leaves, grass, frogs
+ and other things it's
+ not easy being.
+-------- -----------------------
+
+A horizontal rule follows.
+
+***
+
+Here's a definition list:
+
+apples
+ : Good for making applesauce.
+oranges
+ : Citrus!
+tomatoes
+ : There's no "e" in tomatoe.
+
+Again, text is indented 4 spaces. (Put a blank line between each
+term/definition pair to spread things out more.)
+
+Here's a "line block":
+
+| Line one
+| Line too
+| Line tree
+
+and images can be specified like so:
+
+![example image](plume.png "An exemplary image")
+
+Inline math equations go in like so: $\omega = d\phi / dt$. Display
+math should get its own line and be put in in double-dollarsigns:
+
+$$I = \int \rho R^{2} dV$$
+
+And note that you can backslash-escape any punctuation characters
+which you wish to be displayed literally, ex.: \`foo\`, \*bar\*, etc. \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/index.md b/tcllib/examples/httpd/htdocs/index.md
new file mode 100644
index 0000000..af48430
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/index.md
@@ -0,0 +1,22 @@
+Your test server works!
+
+* [Tcllib embedded docs](/tcllib/index.html)
+* [Tcllib's fossil repo (hosted via SCGI)](/fossil)
+* [Standard Markdown Example Page](example.md)
+* [Static HTML Page](html_static_page.html)
+
+A locally served image:
+![Locally Served Image](/tcllib/image/arch_core_container.png "Core Container")
+
+Internal documentation for httpd:
+
+* [Operating Principals](operations.md)
+* [Program Listing for docserver.tcl](docserver.tcl)
+* [Class httpd::reply](reply.md)
+* [Class httpd::server](server.md)
+* [Class httpd::content](content.md)
+ * [Content Server](content.server.md)
+ * [Form handler](content.form.md)
+ * [File handler](content.file.md)
+ * [SCGI handler](content.scgi.md)
+ * [Proxy handler](content.proxy.md) \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/operations.md b/tcllib/examples/httpd/htdocs/operations.md
new file mode 100644
index 0000000..eb9b236
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/operations.md
@@ -0,0 +1,30 @@
+httpd: Operations Manual
+===============================
+[Back to Index](index.md)
+
+The httpd module is designed to be an http server which is embeddable within another project.
+
+1. When a reply socket is opened, the *connect* method is exercised.
+2. The *connect* method then populates a dict with basic information such as the REMOTE_IP and the URI.
+3. *connect* calls the Server's *dispatch* method with this new dict as a parameter.
+4. *dispatch* returns with a dict describing the response to this request, or an empty list to indicate that this is an invalid request.
+5. A new object is created to manage the reply.
+ * If a **class** field is present in the dispatch dict, a new object will be of that class.
+ * If no **class** was given, the new object will be of the class specified by the server's *reply_class* property.
+6. If the field *mixin* is present and non-empty, the new reply object will mixin the class specified.
+7. The server object will then call the reply object's *dispatch* process, with the complete reply description dict as a paramter.
+8. The server adds the object to a list of objects it is tracking. If the reply object does not destroy itself within 2 minutes, the server will destroy it.
+
+Once the *dispatch* method is called, it is the reply object's job to:
+
+1. Parse the HTTP headers of the incoming request
+2. Formulate a response
+3. Transmit that response back across the request socket.
+4. Destroy itself when finished.
+5. On destruction, unregister itself from the server object.
+
+The basic reply class perfoms the following:
+
+1. Reads the HTTP years
+2. Invokes the *content* class, which utilizes the *puts* method to populate an internal buffer.
+3. Invokes the *output* class which will prepare reply headers and output the reply buffer to the request socket.
diff --git a/tcllib/examples/httpd/htdocs/plume.png b/tcllib/examples/httpd/htdocs/plume.png
new file mode 100644
index 0000000..444ec8d
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/plume.png
Binary files differ
diff --git a/tcllib/examples/httpd/htdocs/reply.md b/tcllib/examples/httpd/htdocs/reply.md
new file mode 100644
index 0000000..3ad5741
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/reply.md
@@ -0,0 +1,16 @@
+### Method Url_Decode *data*
+
+Translates a standard http query encoding string into a stream of key/value pairs.
+
+### Method FormData
+
+For GET requests, this method will convert the Query portion of the URI (after the ?)
+to key/value pairs.
+
+For POST requests, this method will read the body of the request and convert
+that block of text to a stream of key/value pairs.
+
+### Method PostData
+
+Returns the raw block of data from the post headers section of the transaction, up to
+the Content-Length specified in the headers. \ No newline at end of file
diff --git a/tcllib/examples/httpd/htdocs/server.md b/tcllib/examples/httpd/htdocs/server.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tcllib/examples/httpd/htdocs/server.md
diff --git a/tcllib/examples/httpd/httpd.tcl b/tcllib/examples/httpd/httpd.tcl
new file mode 100644
index 0000000..ad3e162
--- /dev/null
+++ b/tcllib/examples/httpd/httpd.tcl
@@ -0,0 +1,86 @@
+###
+# Simple webserver example
+###
+
+set DIR [file dirname [file normalize [info script]]]
+set DEMOROOT [file join $DIR htdocs]
+set tcllibroot [file normalize [file join $DIR .. ..]]
+set auto_path [linsert $auto_path 0 [file normalize [file join $tcllibroot modules]]]
+package require httpd
+package require httpd::content
+
+###
+# This script creates two toplevel domains:
+# * Hosting the tcllib embedded documentation as static content
+# * Hosting a local fossil mirror of the tcllib repository
+###
+package require httpd
+
+tool::class create ::docserver::reply::scgi_fossil {
+ superclass httpd::content::scgi
+
+ method scgi_info {} {
+ ###
+ # We could calculate this all out ahead of time
+ # but it's a nice demo to be able to launch the process
+ # and compute the parameters needed on the fly
+ ###
+ set uri [my query_headers get REQUEST_URI]
+ set prefix [my query_headers get prefix]
+ set prefix [string trimright $prefix *]
+ set prefix [string trimright $prefix /]
+ set module tcllib
+ ###
+ #
+ if {![info exists ::fossil_process($module)]} {
+ puts [list GATHERING INFO FOR $module]
+ set info [exec fossil status]
+ set dbfile {}
+ foreach line [split $info \n] {
+ if {[lindex $line 0] eq "repository:"} {
+ set dbfile [string trim [string range $line 12 end]]
+ break
+ }
+ }
+ if {$dbfile eq {}} {
+ tailcall my error 505 "Could not locate fossil respository database"
+ }
+ puts [list LAUNCHING $module $dbfile]
+ package require processman
+ package require nettool
+ set port [::nettool::allocate_port 40000]
+ set handle fossil:$port
+ set mport [my <server> port_listening]
+ set cmd [list fossil server $dbfile --port $port --localhost --scgi 2>/tmp/$module.err >/tmp/$module.log]
+ dict set ::fossil_process($module) port $port
+ dict set ::fossil_process($module) handle $handle
+ dict set ::fossil_process($module) cmd $cmd
+ dict set ::fossil_process($module) SCRIPT_NAME $prefix
+ }
+ dict with ::fossil_process($module) {}
+ if {![::processman::running $handle]} {
+ puts "LAUNCHING $module as $cmd"
+ set process [::processman::spawn $handle {*}$cmd]
+ puts "LAUNCHED"
+ my varname paused
+ after 500
+ puts "RESUMED"
+ }
+ return [list localhost $port $SCRIPT_NAME]
+ }
+}
+tool::class create ::docserver::server {
+ superclass ::httpd::server::dispatch ::httpd::server
+
+
+ method log args {
+ puts [list {*}$args]
+ }
+
+}
+
+::docserver::server create appmain doc_root $DEMOROOT
+appmain add_uri /tcllib* [list mixin httpd::content::file path [file join $tcllibroot embedded www]]
+appmain add_uri /fossil* {mixin ::docserver::reply::scgi_fossil}
+
+tool::main