← All ivy-nodes
IVYXSTUDIO · IVY NODE
H

HTTP Request

ivy.node.http-request · v0.1.0

ivyx✓

Sends one HTTP request to a URL and returns the status, headers and body, parsed as JSON when the response is JSON. Use it to call any REST API or incoming webhook: a Slack message, a GitHub issue, a CRM contact, a row through a REST layer, your own service. The credential is the token input, written as a Bearer header unless auth_header or auth_scheme says otherwise; reference a stored secret for it, and a URL that is itself a secret (an incoming-webhook URL) can be one too. A 4xx or 5xx answer is an error naming the status and the URL unless fail_on_error is false.

#http#webhook#api#rest#network

Inputs

FieldTypeDescription
urlrequiredstringThe address to call, starting with http:// or https://. May reference a stored secret when the URL itself is one.
methodstringThe HTTP method.
headersobjectExtra request headers; a value may reference a stored secret.
paramsobjectQuery parameters added to the URL.
jsonNot declaredThe JSON body to send; sets Content-Type to application/json. Give json or body, not both.
bodystringA raw text body, sent with content_type. Give json or body, not both.
content_typestringThe Content-Type of a raw body.
tokenstringThe credential; reference the stored secret instead of writing it. Sent as '<auth_scheme> <token>' in auth_header.
auth_headerstringThe header the token goes in, for APIs that read X-API-Key or similar.
auth_schemestringThe word before the token; empty sends the token alone.
timeoutnumberSeconds to wait for the answer.
fail_on_errorbooleanWhether a 4xx or 5xx answer is an error. False returns the status and body instead.

Outputs

FieldTypeDescription
statusrequiredintegerThe HTTP status code.
okrequiredbooleanTrue when the status is below 400.
headersobjectThe response headers, names in lowercase.
jsonNot declaredThe response body parsed as JSON, or null when it is not JSON.
textrequiredstringThe response body as text.
urlstringThe final URL after redirects.
elapsed_msintegerHow long the request took.

Source

python

inp = __ivy_ctx__["nodes"][__ivy_node_id__]["input"]

import json
import time
import requests

METHODS = ("GET", "POST", "PUT", "PATCH", "DELETE")

url = inp.get("url")
if not isinstance(url, str) or not url.strip():
    raise ValueError("url is empty; give the address to call.")
url = url.strip()
if not url.lower().startswith(("http://", "https://")):
    raise ValueError(f"url must start with http:// or https://, got {url[:40]!r}.")
method = str(inp.get("method") or "GET").upper()
if method not in METHODS:
    raise ValueError(f"method must be one of {', '.join(METHODS)}, got {method!r}.")

headers = {str(k): str(v) for k, v in (inp.get("headers") or {}).items()}
params = inp.get("params") or {}
if not isinstance(params, dict):
    raise ValueError("params must be an object of query parameters.")
json_body = inp.get("json")
text_body = inp.get("body")
if json_body is not None and text_body is not None:
    raise ValueError("Give json or body, not both.")

token = inp.get("token")
if token:
    header_name = str(inp.get("auth_header") or "Authorization")
    scheme = inp["auth_scheme"] if "auth_scheme" in inp and inp["auth_scheme"] is not None else "Bearer"
    headers[header_name] = (str(scheme).strip() + " " + str(token)).strip()

kwargs = {}
if json_body is not None:
    kwargs["json"] = json_body
elif text_body is not None:
    kwargs["data"] = str(text_body).encode("utf-8")
    headers.setdefault("Content-Type", str(inp.get("content_type") or "text/plain; charset=utf-8"))

timeout = float(inp.get("timeout") or 30)
started = time.time()
try:
    response = requests.request(method, url, headers=headers, params=params, timeout=timeout, **kwargs)
except requests.exceptions.Timeout:
    raise TimeoutError(f"No answer from {url} within {timeout:g} seconds.")
except requests.exceptions.ConnectionError:
    raise ConnectionError(f"Could not reach {url}.")
elapsed_ms = int((time.time() - started) * 1000)

text = response.text
parsed = None
content_type = response.headers.get("Content-Type", "")
if text and ("json" in content_type.lower() or text.lstrip()[:1] in ("{", "[")):
    try:
        parsed = json.loads(text)
    except ValueError:
        parsed = None

fail_on_error = inp.get("fail_on_error")
if fail_on_error is None:
    fail_on_error = True
if fail_on_error and response.status_code >= 400:
    if response.status_code in (401, 403):
        raise PermissionError(f"{response.status_code} from {url}: the credentials were refused.")
    raise RuntimeError(f"{response.status_code} from {url}: {text[:200]}")

out = __ivy_ctx__["nodes"][__ivy_node_id__]["output"]
out["status"] = response.status_code
out["ok"] = response.status_code < 400
out["headers"] = {k.lower(): v for k, v in response.headers.items()}
out["json"] = parsed
out["text"] = text
out["url"] = response.url
out["elapsed_ms"] = elapsed_ms

Tests

Requires: python:3.9, requests

  • get-json

    A GET with query parameters comes back with the JSON the server answered.

  • post-json

    A POST sends its JSON body with the JSON content type.

  • bearer

    A token is sent as a Bearer header.

  • custom-auth-header

    An API that reads X-API-Key gets the token there, with no scheme.

  • text-body

    A raw body goes out as text with the given content type.

  • not-found

    A 404 is an error that names the status and the address.

  • not-found-tolerated

    With fail_on_error false the 404 comes back as a status.

  • both-bodies

    json and body together are refused before any call.