diff --git a/basis/http/http-docs.factor b/basis/http/http-docs.factor new file mode 100644 index 0000000000..100d6c5f06 --- /dev/null +++ b/basis/http/http-docs.factor @@ -0,0 +1,160 @@ +USING: assocs help.markup help.syntax io.streams.string sequences strings present math kernel byte-arrays urls +calendar ; +IN: http + +HELP: +{ $values { "request" request } } +{ $description "Creates an empty request." } ; + +HELP: request +{ $description "An HTTP request." +$nl +"Instances contain the following slots:" +{ $table + { { $slot "method" } { "The HTTP method as a " { $link string } ". The most frequently-used HTTP methods are " { $snippet "GET" } ", " { $snippet "HEAD" } " and " { $snippet "POST" } "." } } + { { $slot "url" } { "The " { $link url } " being requested" } } + { { $slot "version" } { "The HTTP version. Default is " { $snippet "1.1" } " and should not be changed without good reason." } } + { { $slot "header" } { "An assoc of HTTP header values. See " { $link "http.headers" } } } + { { $slot "post-data" } { "See " { $link "http.post-data" } } } + { { $slot "cookies" } { "A sequence of HTTP cookies. See " { $link "http.cookies" } } } +} } ; + +HELP: +{ $values { "response" response } } +{ $description "Creates an empty response." } ; + +HELP: response +{ $class-description "An HTTP response." +$nl +"Instances contain the following slots:" +{ $table + { { $slot "version" } { "The HTTP version. Default is " { $snippet "1.1" } " and should not be changed without good reason." } } + { { $slot "code" } { "HTTP status code, an " { $link integer } ". Examples are 200 for success, 404 for file not found, and so on." } } + { { $slot "message" } { "HTTP status message, only displayed to the user. If the status code is 200, the status message might be ``Success'', for example." } } + { { $slot "header" } { "An assoc of HTTP header values. See " { $link "http.headers" } } } + { { $slot "cookies" } { "A sequence of HTTP cookies. See " { $link "http.cookies" } } } + { { $slot "content-type" } { "an HTTP content type" } } + { { $slot "content-charset" } { "an encoding descriptor. See " { $link "io.encodings" } } } + { { $slot "body" } { "an HTTP response body" } } +} } ; + +HELP: +{ $values { "response" raw-response } } +{ $description "Creates an empty raw response." } ; + +HELP: raw-response +{ $class-description "A minimal HTTP response used by webapps which need full control over all output sent to the client. Most webapps can use " { $link response } " instead." +$nl +"Instances contain the following slots:" +{ $table + { { $slot "version" } { "The HTTP version. Default is " { $snippet "1.1" } " and should not be changed without good reason." } } + { { $slot "code" } { "HTTP status code, an " { $link integer } ". Examples are 200 for success, 404 for file not found, and so on." } } + { { $slot "message" } { "HTTP status message, only displayed to the user. If the status code is 200, the status message might be ``Success'', for example." } } + { { $slot "body" } { "an HTTP response body" } } +} } ; + +HELP: +{ $values { "value" object } { "name" string } { "cookie" cookie } } +{ $description "Creates a cookie with the specified name and value. The value can be any object supported by the " { $link present } " word." } ; + +HELP: cookie +{ $class-description +"An HTTP cookie." +$nl +"Instances contain a number of slots which correspond exactly to the fields of a cookie in the cookie specification:" +{ $table + { { $slot "name" } { "The cookie name, a " { $link string } } } + { { $slot "value" } { "The cookie value, an object supported by " { $link present } } } + { { $slot "comment" } { "A " { $link string } } } + { { $slot "path" } { "The pathname prefix where the cookie is valid, a " { $link string } } } + { { $slot "domain" } { "The domain name where the cookie is valid, a " { $link string } } } + { { $slot "expires" } { "The expiry time, a " { $link timestamp } " or " { $link f } " for a session cookie" } } + { { $slot "max-age" } { "The expiry duration, a " { $link duration } " or " { $link f } " for a session cookie" } } + { { $slot "http-only" } { "If set to a true value, JavaScript code cannot see the cookie" } } + { { $slot "secure" } { "If set to a true value, the cookie is only sent for " { $snippet "https" } " protocol connections" } } +} +"Only one of " { $snippet "expires" } " and " { $snippet "max-age" } " can be set; the latter is preferred and is supported by all modern browsers." } ; + +HELP: delete-cookie +{ $values { "request/response" "a " { $link request } " or a " { $link response } } { "name" string } } +{ $description "Deletes a cookie from a request or response." } +{ $side-effects "request/response" } ; + +HELP: get-cookie +{ $values { "request/response" "a " { $link request } " or a " { $link response } } { "name" string } { "cookie/f" "a " { $link cookie } " or " { $link f } } } +{ $description "Gets a named cookie from a request or response." } ; + +HELP: put-cookie +{ $values { "request/response" "a " { $link request } " or a " { $link response } } { "cookie" cookie } } +{ $description "Stores a cookie in a request or response." } +{ $side-effects "request/response" } ; + +HELP: +{ $values { "raw" byte-array } { "content-type" "a MIME type string" } { "post-data" post-data } } +{ $description "Creates a new " { $link post-data } "." } ; + +HELP: header +{ $values { "request/response" "a " { $link request } " or a " { $link response } } { "key" string } { "value" string } } +{ $description "Obtains an HTTP header value from a request or response." } ; + +HELP: post-data +{ $class-description "HTTP POST data passed in a POST request." +$nl +"Instances contain the following slots:" +{ $table + { { $slot "raw" } { "The raw bytes of the POST data" } } + { { $slot "content" } { "The POST data. This can be in a higher-level form, such as an assoc of POST parameters, a string, or an XML document" } } + { { $slot "content-type" } "A MIME type" } +} } ; + +HELP: set-header +{ $values { "request/response" "a " { $link request } " or a " { $link response } } { "value" object } { "key" string } } +{ $description "Stores a value into the HTTP header of a request or response. The value can be any object supported by " { $link present } "." } +{ $notes "This word always returns the same object that was input. This allows for a ``pipeline'' coding style, where several header parameters are set in a row." } +{ $side-effects "request/response" } ; + +ARTICLE: "http.cookies" "HTTP cookies" +"Every " { $link request } " and " { $link response } " instance can contain cookies." +$nl +"The " { $vocab-link "furnace.sessions" } " vocabulary implements session management using cookies, thus the most common use case can be taken care of without working with cookies directly." +$nl +"The class of cookies:" +{ $subsection cookie } +"Creating cookies:" +{ $subsection } +"Getting, adding, and deleting cookies in " { $link request } " and " { $link response } " objects:" +{ $subsection get-cookie } +{ $subsection put-cookie } +{ $subsection delete-cookie } ; + +ARTICLE: "http.headers" "HTTP headers" +"Every " { $link request } " and " { $link response } " has a set of HTTP headers stored in the " { $slot "header" } " slot. Header names are normalized to lower-case when a request or response is being parsed." +{ $subsection header } +{ $subsection set-header } ; + +ARTICLE: "http.post-data" "HTTP post data" +"Every " { $link request } " where the " { $slot "method" } " slot is " { $snippet "POST" } " can contain post data." +{ $subsection post-data } +{ $subsection } ; + +ARTICLE: "http" "HTTP protocol implementation" +"The " { $vocab-link "http" } " vocabulary contains data types shared by " { $vocab-link "http.client" } " and " { $vocab-link "http.server" } "." +$nl +"The HTTP client sends an HTTP request to the server and receives an HTTP response back. The HTTP server receives HTTP requests from clients and sends HTTP responses back." +$nl +"HTTP requests:" +{ $subsection request } +{ $subsection } +"Requests can contain form submissions:" +{ $subsection "http.post-data" } +"HTTP responses:" +{ $subsection response } +{ $subsection } +"Raw responses only contain a status line, with no header. They are used by webapps which need full control over the HTTP response, for example " { $vocab-link "http.server.cgi" } ":" +{ $subsection raw-response } +{ $subsection } +"Both requests and responses support some common functionality:" +{ $subsection "http.headers" } +{ $subsection "http.cookies" } ; + +ABOUT: "http" diff --git a/basis/urls/urls-docs.factor b/basis/urls/urls-docs.factor index 5d9500788b..c96ecc1f29 100644 --- a/basis/urls/urls-docs.factor +++ b/basis/urls/urls-docs.factor @@ -17,9 +17,9 @@ HELP: >url { $examples "If we convert a string to a URL and print it out again, it will print similarly to the input string, except some normalization may have occurred:" { $example - "USING: accessors io urls ;" + "USING: accessors prettyprint urls ;" "\"http://www.apple.com\" >url ." - "URL\" www.apple.com/\"" + "URL\" http://www.apple.com/\"" } "We can examine the URL object:" { $example @@ -27,9 +27,9 @@ HELP: >url "\"http://www.apple.com\" >url host>> print" "www.apple.com" } - "A relative URL does not have a protocol or host component:" + "A relative URL does not have a protocol, host or port:" { $example - "USING: accessors io urls ;" + "USING: accessors prettyprint urls ;" "\"file.txt\" >url protocol>> ." "f" } @@ -47,13 +47,12 @@ HELP: URL" } ; HELP: assoc>query -{ $values - { "hash" hashtable } - { "str" string } } +{ $values { "assoc" assoc } { "str" string } } { $description "Converts an assoc of query parameters into a query string, performing URL encoding." } { $notes "This word is used to implement the " { $link present } " method on URLs; it is also used by the HTTP client to encode POST requests." } { $examples { $example + "USING: io urls ;" "{ { \"from\" \"Lead\" } { \"to\" \"Gold, please\" } }" "assoc>query print" "from=Lead&to=Gold%2c+please" @@ -134,7 +133,7 @@ HELP: query-param "USING: io urls ;" "URL\" http://food.com/calories?item=French+Fries\"" "\"item\" query-param print" - "\"French Fries\"" + "French Fries" } } ; diff --git a/basis/urls/urls-tests.factor b/basis/urls/urls-tests.factor index 75ee7b6740..d1415a9dde 100644 --- a/basis/urls/urls-tests.factor +++ b/basis/urls/urls-tests.factor @@ -2,19 +2,19 @@ IN: urls.tests USING: urls urls.private tools.test arrays kernel assocs present accessors ; -[ "hello%20world" ] [ "hello world" url-encode ] unit-test +[ "hello+world" ] [ "hello world" url-encode ] unit-test [ "hello world" ] [ "hello%20world" url-decode ] unit-test [ "~hello world" ] [ "%7ehello+world" url-decode ] unit-test [ f ] [ "%XX%XX%XX" url-decode ] unit-test [ f ] [ "%XX%XX%X" url-decode ] unit-test -[ "hello world" ] [ "hello+world" url-decode ] unit-test -[ "hello world" ] [ "hello%20world" url-decode ] unit-test -[ " ! " ] [ "%20%21%20" url-decode ] unit-test -[ "hello world" ] [ "hello world%" url-decode ] unit-test -[ "hello world" ] [ "hello world%x" url-decode ] unit-test -[ "hello%20world" ] [ "hello world" url-encode ] unit-test -[ "%20%21%20" ] [ " ! " url-encode ] unit-test +[ "hello world" ] [ "hello+world" url-decode ] unit-test +[ "hello world" ] [ "hello%20world" url-decode ] unit-test +[ " ! " ] [ "%20%21%20" url-decode ] unit-test +[ "hello world" ] [ "hello world%" url-decode ] unit-test +[ "hello world" ] [ "hello world%x" url-decode ] unit-test +[ "hello+world" ] [ "hello world" url-encode ] unit-test +[ "+%21+" ] [ " ! " url-encode ] unit-test [ "\u001234hi\u002045" ] [ "\u001234hi\u002045" url-encode url-decode ] unit-test diff --git a/basis/urls/urls.factor b/basis/urls/urls.factor index b8b87a92bd..17b309f37f 100644 --- a/basis/urls/urls.factor +++ b/basis/urls/urls.factor @@ -104,8 +104,15 @@ TUPLE: url protocol username password host port path query anchor ; : query-param ( url key -- value ) swap query>> at ; +: delete-query-param ( url key -- url ) + over query>> delete-at ; + : set-query-param ( url value key -- url ) - '[ [ _ _ ] dip ?set-at ] change-query ; + over [ + '[ [ _ _ ] dip ?set-at ] change-query + ] [ + nip delete-query-param + ] if ; : parse-host ( string -- host port ) ":" split1 [ url-decode ] [