George Petasis: This page contains a small example of how an HTTP REST service can be implemented with
Rivet. I don't know if this is the best way to implement such a service, but it works for me (Although my actual implementation uses TclOO). The reason I have created this page, is that the wiki or
Rivet site have no examples on REST service implementations.
package require json
package require json::write
fconfigure stdout -encoding utf-8
##
## Helper functions, for returning results...
##
proc returnJSON {json} {
fconfigure stdout -encoding utf-8
::rivet::headers numeric 200
::rivet::headers type {application/json; charset=utf-8}
::rivet::headers add Content-Length [string bytelength $json]
puts $json
};# returnJSON
proc returnREDIRECT {{id {}}} {
fconfigure stdout -encoding utf-8
::rivet::headers numeric 303
## Create a URI for reading the object with this id...
set scheme [::rivet::env REQUEST_SCHEME]
set host [::rivet::env HTTP_HOST]
set uri [::rivet::env DOCUMENT_URI]
if {$id eq ""} {
::rivet::headers add Location ${scheme}://${host}/${uri}
} else {
::rivet::headers add Location ${scheme}://${host}/${uri}/$id
}
::rivet::no_body
};# returnREDIRECT
##
## Service methods: these methods actually implement the service operations...
##
namespace eval service {
proc index {} {
::json::write array \
[read 0] \
[read 1] \
[read 2]
};# index
proc create {object} {
return id
};# create
proc read {id} {
::json::write object id [::json::write string $id] \
type [::json::write string sample_object]
};# read
proc update {id object} {
puts $object
};# update
proc delete {id} {
};# delete
}
##
## Get the request method...
##
set method [::rivet::env REQUEST_METHOD]
set path [::rivet::env PATH_INFO]
##
## The dispatcher, which examines the uri, and calls the appropriate procedure.
##
switch -exact $path {
{} - / {
## Path is either empty, or "/":
switch -exact $method {
GET {
## index
# -------------------------------------------------------
# method: GET
# path: /
# returns: a list of all objects
returnJSON [service::index]
}
POST {
## create
# -------------------------------------------------------
# method: POST
# path: /
# receives: an object, sent with Content-Type: application/json
# returns: 303 SEE OTHER redirect to the appropriate read endpoint
returnREDIRECT [service::create [::rivet::raw_post]]
}
default {
## Invalid method/post combination. Return an error
::rivet::headers type {text/plain; charset=utf-8}
::rivet::headers numeric 500 ;# HTTP 500: internal server error
if {$path eq ""} {set path /}
puts "invalid method \"$method\" for endpoint \"$path\":"
puts " valid methods are:"
puts " GET: lists all objects"
puts " POST: creates a new object"
::rivet::abort_page
}
}
}
default {
## Path is: /<object id>
switch -glob $path {
/*/ {set id [string range $path 1 end-1]}
default {set id [string range $path 1 end]}
}
switch -exact $method {
GET {
## read
# -------------------------------------------------------
# method: GET
# path: /<id>
# returns: an object
returnJSON [service::read $id]
}
PUT {
## update
# -------------------------------------------------------
# method: PUT
# path: /<id>
# receives: a (partial) object, sent with Content-Type: application/json
# returns: 303 SEE OTHER redirect to the appropriate read endpoint
service::update $id [::rivet::raw_post]
returnREDIRECT ;# No need to specify an id, the endpoint is the same
}
DELETE {
## delete
# -------------------------------------------------------
# method: DELETE
# path: /<id>
# returns: 204 NO CONTENT, and -- obviously -- no content
service::delete $id
::rivet::headers numeric 204
::rivet::no_body
}
default {
## Invalid method/post combination. Return an error
headers type {text/plain; charset=utf-8}
headers numeric 500 ;# HTTP 500: internal server error
if {$path eq ""} {set path /}
puts "invalid method \"$method\" for endpoint \"$path\":"
puts " valid methods are:"
puts " GET: returns the object with id: \"$id\""
puts " PUT: updates the object with id: \"$id\""
puts " DELETE: deletes the object with id: \"$id\""
::rivet::abort_page
}
}
}
}