All Projects → Southclaws → pawn-requests

Southclaws / pawn-requests

Licence: MIT license
pawn-requests provides an API for interacting with HTTP(S) JSON APIs.

Programming Languages

Pawn
127 projects
C++
36643 projects - #6 most used programming language
CMake
9771 projects
Earthly
7 projects

Projects that are alternatives of or similar to pawn-requests

Restclient
🦄 Simple HTTP and REST client for Unity based on Promises, also supports Callbacks! 🎮
Stars: ✭ 675 (+1105.36%)
Mutual labels:  http-client, http-requests, requests
pawn-stdlib
The Pawn Standard Library Package, not including any files related to SA:MP - designed for the sampctl package management system.
Stars: ✭ 13 (-76.79%)
Mutual labels:  sa-mp, sa-mp-development, pawn-package
code-parse.inc
Pre-processor macros for analysing PAWN functions.
Stars: ✭ 23 (-58.93%)
Mutual labels:  sa-mp, sa-mp-development, pawn-package
sscanf
SA:MP sscanf plugin
Stars: ✭ 33 (-41.07%)
Mutual labels:  sa-mp, pawn-package, sa-mp-plugin
samp-node-lib
NodeJS library for Scripting San Andreas Multiplayer:SAMP depends on samp-node plugin
Stars: ✭ 23 (-58.93%)
Mutual labels:  sa-mp, sa-mp-development, sa-mp-plugin
Node Request Retry
💂 Wrap NodeJS request module to retry http requests in case of errors
Stars: ✭ 330 (+489.29%)
Mutual labels:  http-client, http-requests, requests
Httpu
The terminal-first http client
Stars: ✭ 619 (+1005.36%)
Mutual labels:  http-client, http-requests
Gout
gout to become the Swiss Army Knife of the http client @^^@---> gout 是http client领域的瑞士军刀,小巧,强大,犀利。具体用法可看文档,如使用迷惑或者API用得不爽都可提issues
Stars: ✭ 749 (+1237.5%)
Mutual labels:  http-client, http-requests
Requests3
Requests 3.0, for Humans and Machines, alike. 🤖
Stars: ✭ 813 (+1351.79%)
Mutual labels:  http-client, requests
Easygo
基于Kotlin、OkHttp的声明式网络框架,像写HTML界面一样写网络调用代码
Stars: ✭ 40 (-28.57%)
Mutual labels:  http-client, http-requests
Http Shortcuts
Android app to create home screen shortcuts that trigger arbitrary HTTP requests
Stars: ✭ 329 (+487.5%)
Mutual labels:  http-client, http-requests
Uplink
A Declarative HTTP Client for Python
Stars: ✭ 824 (+1371.43%)
Mutual labels:  http-client, requests
EthernetWebServer SSL
Simple TLS/SSL Ethernet WebServer, HTTP Client and WebSocket Client library for for AVR, Portenta_H7, Teensy, SAM DUE, SAMD21, SAMD51, STM32F/L/H/G/WB/MP1, nRF52 and RASPBERRY_PI_PICO boards using Ethernet shields W5100, W5200, W5500, ENC28J60 or Teensy 4.1 NativeEthernet/QNEthernet. It now supports Ethernet TLS/SSL Client. The library supports …
Stars: ✭ 40 (-28.57%)
Mutual labels:  http-client, http-requests
Requests
Convenient http client for java, inspired by python request module
Stars: ✭ 459 (+719.64%)
Mutual labels:  http-client, http-requests
Guzzle
Guzzle, an extensible PHP HTTP client
Stars: ✭ 21,384 (+38085.71%)
Mutual labels:  http-client, requests
Karin
An elegant promise based HTTP client for the browser and node.js [WIP]
Stars: ✭ 393 (+601.79%)
Mutual labels:  http-client, http-requests
Saber
⚔️ Saber, PHP异步协程HTTP客户端 | PHP Coroutine HTTP client - Swoole Humanization Library
Stars: ✭ 866 (+1446.43%)
Mutual labels:  http-client, requests
Frequest
FRequest - A fast, lightweight and opensource desktop application to make HTTP(s) requests
Stars: ✭ 130 (+132.14%)
Mutual labels:  http-client, http-requests
Rxios
A RxJS wrapper for axios
Stars: ✭ 119 (+112.5%)
Mutual labels:  http-client, requests
Python Simple Rest Client
Simple REST client for python 3.6+
Stars: ✭ 143 (+155.36%)
Mutual labels:  http-client, requests

pawn-requests

GitHub

This package provides an API for interacting with HTTP(S) APIs with support for text and JSON data types.

Installation

Simply install to your project:

sampctl package install Southclaws/pawn-requests

Include in your code and begin using the library:

#include <requests>

Usage

Requests

The Requests API is based on common implementations of similar libraries in languages such as Go, Python and JS (Node.js).

There is an example of a basic gamemode that uses requests to store player data as JSON here.

Requests Client

First you create a RequestsClient, you should store this globally:

new RequestsClient:client;

main() {
    client = RequestsClient("http://httpbin.org/");
}

When you create a RequestsClient, you specify the endpoint you want to send requests to with that client. This means you don't specify the endpoint for each individual request.

You can also set headers for the client, these headers will be sent with every request. This is useful for setting authentication headers for a private endpoint:

new RequestsClient:client;

main() {
    client = RequestsClient("http://httpbin.org/", RequestHeaders(
        "Authorization", "Bearer xyz"
    ));
}

The RequestHeaders function expects an even number of string arguments. It's good practice to lay out your headers in a key-value style, like:

RequestHeaders(
    "Authorization", "Bearer xyz",
    "Connection", "keep-alive",
    "Cache-Control", "no-cache"
)

But don't forget these are just normal arguments to a function so watch out for trailing commas!

Making Basic Requests

Now you have a client, you can start making requests. If you want to work with plain text or any data other than JSON, you use the Request function:

Request(
    client,
    "robots.txt",
    HTTP_METHOD_GET,
    "OnGetData",
    .headers = RequestHeaders()
);

public OnGetData(Request:id, E_HTTP_STATUS:status, data[], dataLen) {
    printf("status: %d, data: '%s'", _:status, data);
}

Using the client constructed earlier, this would hit http://httpbin.org/robots.txt with a GET request and when the request has finished, OnGetData would be called and print:

status: 200, data: 'User-agent: *
Disallow: /deny
'

The behaviour is similar to the existing SA:MP HTTP() function except this supports headers, a larger body, more methods, HTTPS and is generally safer in terms of error handling.

Making JSON Requests

JSON requests allow you to inline construct JSON at the request side as well as access JSON objects in the response.

For example, the endpoint http://httpbin.org/anything returns JSON data so we can access that directly as a Node: object in the response callback:

RequestJSON(
    client,
    "anything",
    HTTP_METHOD_GET,
    "OnGetJson",
    .headers = RequestHeaders()
);

public OnGetJson(Request:id, E_HTTP_STATUS:status, Node:node) {
    new output[128];
    JsonGetString(node, "method", output);
    printf("anything response: '%s'", output);
}

The anything endpoint at httpbin responds with a bunch of related data in JSON format. The method field contains the method used to perform the request and in this case, the method is GET so OnGetJson will output anything response: 'GET'.

And you can also send JSON data with a POST method:

RequestJSON(
    client,
    "post",
    HTTP_METHOD_POST,
    "OnPostJson",
    JsonObject(
        "playerName", JsonString("Southclaws"),
        "kills", JsonInt(5),
        "topThreeWeapons", JsonArray(
            JsonString("M4"),
            JsonString("MP5"),
            JsonString("Desert Eagle")
        )
    ),
    .headers = RequestHeaders()
);

public OnPostJson(Request:id, E_HTTP_STATUS:status, Node:node) {
    if(status == HTTP_STATUS_CREATED) {
        printf("successfully posted JSON!");
    }
}

You could quite easily build a JSON-driven storage server backed by MongoDB.

See the JSON section below for examples of manipulating JSON Node: objects.

See the pawn-requests-example repository for a more full example of using requests and JSON together.

Request Failures

If a request fails for any reason, OnRequestFailure is called with the following signature: (Request:id, errorCode, errorMessage[], len) where errorCode and errorMessage contain information to help you debug the request.

Keeping Track of Request IDs

Both Request and RequestJSON return a Request: tagged value. This value is the request identifier and is unique during server runtime, same as how SetTimer returns a unique ID.

Because responses are asynchronous and the data comes back in a callback at a later time, most of the time you will have to store this ID so you know which request triggered which response.

You cannot simply use the ID as an index to an array because it's an automatically incrementing value and thus is unbounded. You should instead use BigETI's pawn-map plugin to map request IDs to some other data - such as the player/vehicle/house/etc that triggered the request. See the pawn-requests-example for an example of this.

JSON

If you don't already know what JSON is, a good place to start is MDN. It's pretty much a web API standard nowadays (Twitter, Discord, GitHub and just about every other API uses it to represent data). I'll briefly go over it before getting into the API.

This plugin stores JSON values as "Nodes". Each node represents a value of one type. Here are some examples of the representations of different node types:

  • {} - Object that is empty
  • {"key": "value"} - Object with one key that points to a String node
  • "hello" - String
  • 1 - Number (integer)
  • 1.5 - Number (floating point)
  • [1, 2, 3] - Array, of Number nodes
  • [{}, {}] - Array of empty Object nodes
  • true - Boolean

The main point here is that everything is a node, even Objects and Arrays that contain other nodes.

Building an Object

To build a JSON object to be sent in a request, you most likely want to start with JsonObject however you can use any node as the root node, it depends on where you're sending the data but for this example I'll use an Object as the root node.

new Node:node = JsonObject();

This just constructs an empty object and if you "stringify" it (stringify simply means to turn into a string) you get:

{}

So to add more nodes to this object, simply add parameters, as key-value pairs:

new Node:node = JsonObject(
    "key", JsonString("value")
);

This would stringify as:

{
    "key": "value"
}

You can nest objects within objects too:

new Node:node = JsonObject(
    "key", JsonObject(
        "key", JsonString("value")
    )
);
{
    "key": {
        "key": "value"
    }
}

And do arrays of any node:

new Node:node = JsonObject(
    "key", JsonArray(
        JsonString("one"),
        JsonString("two"),
        JsonString("three"),
        JsonObject(
            "more_stuff1", JsonString("uno"),
            "more_stuff2", JsonString("dos"),
            "more_stuff3", JsonString("tres")
        )
    )
);

See the unit tests for more examples of JSON builders.

Accessing Data

When you request JSON data, it's provided as a Node: in the callback. Most of the time, you'll get an object back but depending on the application that responded this could differ.

Lets assume this request responds with the following data:

{
    "name": "Southclaws",
    "score": 45,
    "vip": true,
    "inventory": [
        {
            "name": "M4",
            "ammo": 341
        },
        {
            "name": "Desert Eagle",
            "ammo": 32
        }
    ]
}
public OnSomeResponse(Request:id, E_HTTP_STATUS:status, Node:json) {
    new ret;

    new name[MAX_PLAYER_NAME];
    ret = JsonGetString(node, "name", name);
    if(ret) {
        err("failed to get name, error: %d", ret);
        return 1;
    }

    new score;
    ret = JsonGetInt(node, "score", score);
    if(ret) {
        err("failed to get score, error: %d", ret);
        return 1;
    }

    new bool:vip;
    ret = JsonGetBool(node, "vip", vip);
    if(ret) {
        err("failed to get vip, error: %d", ret);
        return 1;
    }

    new Node:inventory;
    ret = JsonGetArray(node, "inventory", inventory);
    if(ret) {
        err("failed to get inventory, error: %d", ret);
        return 1;
    }

    new length;
    ret = JsonArrayLength(inventory, length);
    if(ret) {
        err("failed to get inventory array length, error: %d", ret);
        return 1;
    }

    for(new i; i < length; ++i) {
        new Node:item;
        ret = JsonArrayObject(inventory, i, item);
        if(ret) {
            err("failed to get inventory item %d, error: %d", i, ret);
            return 1;
        }

        new itemName[32];
        ret = JsonGetString(item, "name", itemName);
        if(ret) {
            err("failed to get inventory item %d, error: %d", i, ret);
            return 1;
        }

        new itemAmmo;
        ret = JsonGetInt(item, "name", itemAmmo);
        if(ret) {
            err("failed to get inventory item %d, error: %d", i, ret);
            return 1;
        }

        printf("item %d name: %s ammo: %d", itemName, itemAmmo);
    }

    return 0;
}

In this example, we extract each field from the JSON object with full error checking. This example shows usage of object and array access as well as primitives such as strings, integers and a boolean.

If you're not a fan of the overly terse and explicit error checking, you can alternatively just check your errors at the end but this will mean you won't know exactly where an error occurred, just that it did.

new ret;
ret += JsonGetString(node, "key1", value1);
ret += JsonGetString(node, "key2", value2);
ret += JsonGetString(node, "key3", value3);
if(ret) {
    err("some error occurred: %d", ret);
}

Testing

To run unit tests for the plugin on Windows, first build the plugin with Visual Studio by opening the CMakeLists.txt via the File > Open > CMake menu and then building the project. You will need to pull the dependencies too so make sure you've done git submodule init && git submodule update or cloned the repository recursively.

Once you've done that, the .dll files will be in ./test/plugins/Debug. There is also a -release suffixed version of this make command for testing the release binaries.

make test-windows-debug

If you want to build and test the Linux version from a Windows machine, make sure Docker is installed and run:

make build-linux

Which will output requests.so to ./test/plugins. To run unit tests on Linux, run:

make test-linux

Which will run the tests via sampctl with the --container flag set.

Development

To set up the development environment, first install vcpkg then cpprestsdk.

Open Visual Studio (A recent version with CMake support) and File > Open the project CMakeLists.txt. VS will fail on the first attempt as it won't be able to find cpprestsdk. To resolve this, edit .vs/CMakeSettings.json to contain the necessary environment variables for a Debug and Release configuration:

{
  "configurations": [
    {
      "name": "x86-Release",
      "generator": "Visual Studio 15 2017",
      "configurationType": "Release",
      "buildRoot":
        "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-m -v:minimal",
      "variables": [
        {
          "name": "CMAKE_TOOLCHAIN_FILE",
          "value": "C:/Users/Southclaws/vcpkg/scripts/buildsystems/vcpkg.cmake"
        }
      ]
    },
    {
      "name": "x86-Debug",
      "generator": "Visual Studio 15 2017",
      "configurationType": "Debug",
      "buildRoot":
        "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}",
      "cmakeCommandArgs": "",
      "buildCommandArgs": "-m -v:minimal",
      "variables": [
        {
          "name": "CMAKE_TOOLCHAIN_FILE",
          "value": "C:/Users/Southclaws/vcpkg/scripts/buildsystems/vcpkg.cmake"
        }
      ]
    }
  ]
}

The configuration file may change depending on VS version or other things, as long as the CMAKE_TOOLCHAIN_FILE variables are passed to CMake properly, the build should succeed.

Once this is done, VS should start indexing all the dependencies. Once it has finihed, in the menu bar, hit CMake > Build All and it should spit out a .dll.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].