Skip to content

Calling Web Services

@http sends an HTTP request from softcode. When the reply arrives, the game queues an action list of your choosing and hands it the reply. Use it to post to a webhook, ask a dice or weather service for data, or read from a site’s API.

@http <obj>/<attr>=<URL>[,<headers>]
@http/<verb> <obj>/<attr>=<URL>[,<data>[,<headers>]]

Without a switch the request is a GET. The other verbs are switches: /post, /put, /delete, /patch, /head, /options, /trace and /connect.

Only Wizards and objects with the Can_HTTP power may use @http.

The examples on this page use postman-echo.com, which answers with the request it received, so the reply shows what @http sent. SharpMUSH’s test suite runs them against that site, so the output shown is what the game prints.

The action list receives the reply as named arguments:

ArgumentHolds
%0the body of the reply
%<status>the HTTP status, such as 200
%<content-type>the reply’s Content-Type header
%<headers>every header of the reply, as a JSON object
%<http-overflow>1 if the body was too long for one value and was cut short, otherwise 0

A body is cut at the most one value can hold (5 MiB). When that happens, the object holding the attribute is also told Too much HTTP data received; excess truncated.

&SHOW me=think [if(%<http-overflow>,Reply too long.,Reply complete.)]
@http me/SHOW=https://postman-echo.com/get

Output: Reply complete.

In %<headers> each header name is in lower case. Its value is a string, or a list of strings when the site sent that header more than once. json_query() reads one; get returns it as a JSON string, quotes and all, and unescape turns that back into text:

&SHOW me=think [json_query(json_query(%<headers>,get,content-type),unescape)]
@http me/SHOW=https://postman-echo.com/get

Output: application/json; charset=utf-8

<data> is the body of the request. <headers> is a JSON object: each member is a header name, and its value is a string, a stored secret, or a list of those to send that header once for each. json() builds one. A GET has no body, so its headers come straight after the URL.

&SHOW me=think %<status> [json_query(%0,extract,$.json.bar)] [json_query(%0,extract,$.headers.game)]
@http/post me/SHOW=https://postman-echo.com/post,json(object,bar,"baz"),json(object,Content-Type,"application/json",Game,"MyMUSH")

Output: 200 baz MyMUSH

How the headers are used:

  • Content-Type sets the type of <data>. Without it, data is sent as application/x-www-form-urlencoded, the type of an HTML form.
  • The data is encoded in the charset its type names (text/plain; charset=ISO-8859-1), or UTF-8 when it names none.
  • User-Agent replaces SharpMUSH’s own.
  • Every other header is sent as given.

@http refuses the whole request when <headers> is not an object, a value is not a string, a secret or a list of those, a header name has a space in it, or it sets Content-Length (the data sets that). It says:

Headers must be a JSON object of header names and values. Aborting.

A JSON object typed into the command needs a second pair of braces, because the outer pair only groups the argument and is removed:

&SHOW me=think %<status> [json_query(%0,extract,$.headers.game)]
@http me/SHOW=https://postman-echo.com/get,{{"Game":"MyMUSH"}}

Output: 200 MyMUSH

Send an Authorization header. For HTTP Basic, the value is Basic followed by encode64(<user>:<password>):

&SHOW me=think %<status> %0
@http me/SHOW=https://postman-echo.com/basic-auth,json(object,Authorization,"Basic [encode64(postman:password)]")

Output: 200 {"authenticated":true}

A site that issues API tokens takes them the same way, as json(object,Authorization,"Bearer <token>").

A password or token written into softcode can be read by anyone who can read that attribute, and it is in every copy of the world. Store it as a secret instead. Softcode then names the secret, and the server puts the value into the header as the request leaves. No softcode ever holds the value.

A wizard stores the secret and names the hosts it may go to:

> @secret http.postman.login=postman:password
http.postman.login is set. Its value is never shown again.
> @secret/host http.postman.login=postman-echo.com
@http may send http.postman.login to: postman-echo.com

In the headers, a secret is an object with secret, its name, and an optional scheme:

&SHOW me=think %<status> %0
@http me/SHOW=https://postman-echo.com/basic-auth,json(object,Authorization,json(object,secret,"http.postman.login",scheme,"Basic"))

Output: 200 {"authenticated":true}

The scheme decides how the value is written:

schemeThe header carries
nonethe value alone
BasicBasic and the value, <user>:<password>, base64-encoded
anything else, such as Bearerthe scheme, a space, and the value

@http sends a secret only when all of these hold, and otherwise refuses the whole request with A secret in the headers may not be sent to that URL. Aborting.:

  • The secret’s name starts with http.. Secrets the server keeps for itself, such as sql.password, can never be sent.
  • The URL is https.
  • The URL’s host is one @secret/host named for that secret. *.example.com covers every host under example.com, but not example.com itself.

A request that carries a secret does not follow redirects, so a site cannot pass the secret on to another host. The action list gets the redirect reply itself, with its Location in %<headers>.

PennMUSH hands softcode q-registers instead of named arguments. A game imported from PennMUSH has the http_qregisters option on, and then @http also:

  • sets %q<status>, %q<content-type> and %q<http-overflow> for the action list;
  • reads %q<content-type> as the type of <data> when no <headers> are given;
  • reads %q<userpass>, of the form <user>:<password>, and sends it as an HTTP Basic login.

So imported code such as this keeps working:

@http me/SHOW=[setq(userpass,postman:password)]https://postman-echo.com/basic-auth

Without that option, @http reads no q-register. A q-register lasts until the end of the queue entry, so one left over from other code could otherwise decide where a password is sent.

PennMUSH waits for the site to ask for a login before it sends userpass. SharpMUSH sends it as HTTP Basic at once, so a site that accepts only Digest logins cannot be reached this way.

A PennMUSH build with a headers argument takes it as Name:value lines, where SharpMUSH takes a JSON object. In both, <headers> replaces %q<content-type>.