SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Plugin Development

Reply
 
Thread Tools Display Modes
Old 02/05/2018, 02:56 PM   #1
[HLF]Southclaw
High-roller
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 4,947
Reputation: 1510
Default HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

pawn-requests



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:

Code:
sampctl package install Southclaws/pawn-requests
Include in your code and begin using the library:

Code:
#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:

Code:
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:

Code:
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:

Code:
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:

Code:
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:

Code:
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:

Code:
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:

Code:
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 OnGetJson(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 (*******, 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.

Code:
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:

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

Code:
new Node:node = JsonObject(
    "key", JsonString("value")
);
This would stringify as:

PHP Code:
{
    
"key""value"

You can nest objects within objects too:

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

And do arrays of any node:

Code:
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:

PHP Code:
{
    
"name""Southclaws",
    
"score"45,
    
"vip"true,
    
"inventory": [
        {
            
"name""M4",
            
"ammo"341
        
},
        {
            
"name""Desert Eagle",
            
"ammo"32
        
}
    ]

Code:
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.

Code:
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.

Code:
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:

Code:
make build-linux
Which will output requests.so to ./test/plugins. To run unit tests on Linux, run:

Code:
make test-linux
Which will run the tests via sampctl with the --container flag set.
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 02/05/2018, 02:58 PM   #2
Gforcez1337
High-roller
 
Gforcez1337's Avatar
 
Join Date: Jul 2010
Location: The Netherlands.
Posts: 1,110
Reputation: 367
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

This looks like a great way to seperate database (back-end) stuff with the gamemode itself. Looks great!
__________________

Gforcez1337 is offline   Reply With Quote
Old 02/05/2018, 03:00 PM   #3
Battlezone
Gangsta
 
Battlezone's Avatar
 
Join Date: Aug 2013
Location: Berlin
Posts: 859
Reputation: 155
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Much needed for my server, thanks
__________________


Interested in up to 500 slots hosting for $5 only? PM me

Battlezone is offline   Reply With Quote
Old 02/05/2018, 05:36 PM   #4
[HLF]Southclaw
High-roller
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 4,947
Reputation: 1510
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Quote:
Originally Posted by Gforcez1337 View Post
This looks like a great way to seperate database (back-end) stuff with the gamemode itself. Looks great!
I wrote this with that exact idea in mind!

Also, the next stable version will have a websockets client built in for bidirectional websocket communication.
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 02/05/2018, 05:48 PM   #5
DeitY
Huge Clucker
 
DeitY's Avatar
 
Join Date: Aug 2012
Location: Serbia
Posts: 319
Reputation: 14
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Magnificent release.
__________________
Serbian RolePlay Server! 5+ Years SAMP server, 3+ years highest number of players!

DeitY is offline   Reply With Quote
Old 02/05/2018, 06:43 PM   #6
sampreader
Little Clucker
 
Join Date: Dec 2011
Posts: 37
Reputation: 35
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Excellent explanations and good use of linking to additional information, very impressed, good job.
sampreader is offline   Reply With Quote
Old 02/05/2018, 09:22 PM   #7
IS4
Banned
 
Join Date: Apr 2018
Posts: 15
Reputation: 66
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Very nice! PawnPlus compatibility when?
IS4 is offline   Reply With Quote
Old 03/05/2018, 12:10 AM   #8
Type-R
Huge Clucker
 
Type-R's Avatar
 
Join Date: Feb 2011
Posts: 302
Reputation: 5
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

I have been learning and using some API's for the past semester in my web development class in College. But my question is, what would be the benefit of using this instead of MySQL?
__________________
Type-R is offline   Reply With Quote
Old 03/05/2018, 09:08 AM   #9
[HLF]Southclaw
High-roller
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 4,947
Reputation: 1510
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Version 0.3.0 just released with a basic WebSocket API. This means you can trigger events instantaneously in your Pawn script from external sources such as:

- Chat messages from some WebSocket based chat system
- Events on a forum, such as faction board updates or group events
- Discord messages or role changes etc, in case you don't want to use the Discord plugin
- User actions on a control panel such as purchases or character updates
- Price changes for items/cars/houses


Quote:
Originally Posted by Type-R View Post
I have been learning and using some API's for the past semester in my web development class in College. But my question is, what would be the benefit of using this instead of MySQL?
I'm not sure what you mean, can you elaborate? MySQL is a database and this is a plugin that allows you to make web requests and work with JSON.
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 03/05/2018, 09:09 AM   #10
Gforcez1337
High-roller
 
Gforcez1337's Avatar
 
Join Date: Jul 2010
Location: The Netherlands.
Posts: 1,110
Reputation: 367
Default Re: HTTP(S) Requests+JSON Plugin - Make HTTP(S) Requests with JSON

Quote:
Originally Posted by Type-R View Post
I have been learning and using some API's for the past semester in my web development class in College. But my question is, what would be the benefit of using this instead of MySQL?
Your gamemode is not directly 'connected' to the SQL Server, this way you seperate the data with the gamemode. Why is this a benefit? You can build your API however you want, you can use your prefered DBMS. This gives you flexibility in how you want to deal with your data. Also the data will be handled by the API and not by the SA:MP Server, so if you have a lot of data that you want to process, your API will handle that and won't stress your SA:MP server. Which can be a big stress relief for your server which can reduce lag.

Also, if you want to create an UCP or any other type of application or website that needs data from your gamemode, you can access that via the same API so you don't have to rebuild queries in your UCP, just send a get request to the API and you got the information you need.
__________________

Gforcez1337 is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[Plugin] SAMPSON - A JSON plugin for SA-MP KingHual Plugin Development 27 18/07/2018 03:26 PM
JSON and SAMP HTTP davidbull Scripting Help 0 05/09/2016 06:07 AM
[HELP] Vehicles.json don't load Stefand Scripting Help 1 21/02/2012 02:19 PM
HTTP() callback won't fire, + server crash using HTTP() KoczkaHUN Bug Reports 6 14/12/2011 09:35 AM
vehicles.json help plz sherlock Server Support 3 02/01/2011 12:41 PM


All times are GMT. The time now is 05:17 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.