Welcome to Open Carnage

A resource for Halo 1 modding and tech, with unique means of rewarding individual content creation and support. Have a wander to see why we're worth the time! EST. 2012

Sign in to follow this  
Followers 0
Kavawuvi

SAPP HTTP Client

This tool will give your SAPP script basic HTTP functionality. Here is an example snippet of code that will get your script working:

 

ffi = require("ffi")
ffi.cdef [[
    typedef void http_response;
    http_response *http_get(const char *url);
    void http_destroy_response(http_response *);
    bool http_response_is_null(http_response *);
    const char *http_read_response(const http_response *);
    uint32_t http_response_length(const http_response *);
]]
http_client = ffi.load("lua_http_client")

function GetPage(URL)
    local response = http_client.http_get(URL,true)
    local returning = nil
    if http_client.http_response_is_null(response) ~= true then
        local response_text_ptr = http_client.http_read_response(response)
        returning = ffi.string(response_text_ptr)
    end
    http_client.http_destroy_response(response)
    return returning
end
 

To install, copy the .dll files into your server folder. That's it! The included script will pull a random sequence between 1 and 10 as well as get your server's public IP, all using HTTPS.

 

Here is a list of functions with relevant notes on what they do and how they work:

 

http_get(string URL, boolean async) --> pointer Response (added in 1.0; "async" argument added in 1.1)
This function will attempt to access URL. Setting async to false will block whatever thread is being used, which means that the game logic will not continue until it's done if run on the main thread. If an error occurs, it will be printed to the console, and a null pointer will be returned.

http_destroy_response(pointer Response) (added in 1.0)
This function will destroy the response, freeing up any memory being used. This is necessary to prevent memory leakage and should be used when you are done with the response. It is safe to destroy a null pointer, but using a destroyed pointer can crash the server.
 

http_response_is_null(pointer Response) --> boolean Null (added in 1.0)

This function will return true if the request has failed or if there is no response yet.

 

http_read_response(pointer Response) --> pointer Data (added in 1.0)
This function will give you the pointer to the enclosed data of the response. You can then convert it to a string with ffi.string(pointer Data) or convert it to a numerical representation of the pointer for use with SAPP's read_XXXX functions with tonumber(ffi.cast("uint32_t",pointer Data)). The data is automatically null-terminated, if necessary, and will be destroyed when the response pointer is destroyed. This function does not check if the response is successful. If it's invalid, the server will crash.

http_response_length(pointer Response) --> number Length (added in 1.0)
This function will give you the length of the response data in bytes, not including the additional null termination for strings. This function does not check if the response is successful. If it's invalid, the server will crash.

 

http_wait_async(pointer Response) (added in 1.1)
This function will wait until the HTTP query is finished, blocking the main thread.

 

http_response_received(pointer Response) -> boolean Received (added in 1.1)
If the function has finished its query, this function will return true. This does not necessarily mean that an error hasn't occurred.

 

 

 

Download (1.1): lua_http_client_1_1.7z

Download previous version (1.0): lua_http_client.7z

 

Note: The script included is only to test the installation of the client and is not intended for production environments if left unmodified. This is NOT a SAPP script release, which is why it's not published under SAPP Script Releases.

WaeV, Tucker933, NeX and 1 other like this

Share this post


Link to post
Share on other sites

Members of Open Carnage never see off-site ads.

The response returns null if it contains any character between 0x80 and 0xFF?

Fixed! I also fixed a couple other things, though they were mainly just typos (mainly, checking if it was null was broken, and the above example was broken. all was fixed).

 

Lastly, I added http_response_length which will read the number of bytes received from the stream, returning 0 if it's null or otherwise empty.

giraffe likes this

Share this post


Link to post
Share on other sites

I don't have a server so don't use any of this but just wanted to say congrats on all the advances you've made with SAPP.

Kavawuvi, WaeV and Sceny like this

Umh7x1l.gif

Share this post


Link to post
Share on other sites

Forgot to note, you CAN actually use downloaded data with SAPP's read_XXXX functions if you convert it to a numerical pointer. Example:

local response = http_client.http_get("https://dl.dropboxusercontent.com/u/30298900/ui.map", true)
local length = http_client.http_response_length(response)
cprint("Downloaded " .. (math.ceil(length / 1000 / 1000 * 100) / 100) .. " megabytes.")
if length < 0x800 then
    cprint("Failed to retrieve the map.")
    http_client.http_destroy_response(response)
    return
end

local map_pointer = tonumber(ffi.cast("uint32_t",http_client.http_read_response(response)))

local head = read_dword(map_pointer + 0) == 0x68656164
local foot = read_dword(map_pointer + 0x7FC) == 0x666F6F74

if head == false or foot == false then
    cprint("Map is invalid.")
    http_client.http_destroy_response(response)
    return
end

cprint("Map Name: " .. read_string(map_pointer + 0x20))
cprint("Map Build Version: " .. read_string(map_pointer + 0x40))

local file_size_in_header = read_dword(map_pointer + 0x8)
cprint("File size in header: " .. file_size_in_header .. " bytes...")

if(file_size_in_header == length) then
    cprint("File size in header is correct.")
else
    cprint("File size in header is wrong. It is off by " .. math.abs(file_size_in_header - length) .. " bytes.")
end

local meta_data = read_dword(map_pointer + 0x10)
if meta_data > length then
    cprint("Tag data is located outside of the map file for some reason. Backing off...")
    http_client.http_destroy_response(response)
    return
end
local scnr_tag_id = read_word(map_pointer + meta_data + 0x4)

local scnr_tag_entry = read_dword(map_pointer + meta_data) - 0x40440000 + scnr_tag_id * 0x20 + meta_data
if scnr_tag_entry < 0 or scnr_tag_entry > length then
    cprint("Scnr tag is located outside of the map file for some reason. Backing off...")
    http_client.http_destroy_response(response)
    return
end

local scnr_tag_name_pointer = read_dword(map_pointer + scnr_tag_entry + 0x10) - 0x40440000 + meta_data
if scnr_tag_name_pointer < 0 or scnr_tag_name_pointer > length then
    cprint("Scnr tag path is located outside of the map file for some reason. Backing off...")
    http_client.http_destroy_response(response)
    return
end

cprint("Principal scenario tag: " .. read_string(map_pointer + scnr_tag_name_pointer))

http_client.http_destroy_response(response)

In the console, this shows up as:

Downloaded 2.65 megabytes.
Map Name: ui
Map Build Version: 01.00.00.0563
File size in header: 2649984 bytes...
File size in header is correct.
Principal scenario tag: levels\ui\ui

This is probably a little more than what most people will do, but it works. As usual, using a pointer after it's been destroyed will crash this server. However, you do need to destroy pointers if you want to prevent memory leaks.

Share this post


Link to post
Share on other sites

I've updated this asset to include a second argument to http_get. If it is true, then the request will be done asynchronously, which will prevent the request from blocking the main thread, allowing game logic to continue as the request is being made. This is useful for requests that need to made during the game while other players are present. Setting it to false will use the same functionality as before, which is simpler but will halt game logic until the request is finished as usual.

 

You can then check if the request is completed using the functions listed in the OP periodically using timers. Note that SAPP timers only accept string arguments (the request cannot be converted to a string), so you will need to store the request using a global variable.

 

I've also included the source code.

WaeV and NeX like this

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0
  • Recently Browsing   0 members

    No registered users viewing this page.