Tutorial: HTTP requests

In this tutorial you will learn how to handle incoming HTTP requests in your app service.

For that we build a simple REST API. A very simple one as you will see. It stores a single text, that can be accessed using the URL http://<app-platform-addr>/<app-name>/value. We implement GET, PUT and DELETE operations for reading, storing and deleting the text.

Conventions

The used file and class names in this example are based on a newly created App with the name NewApp1 and the company name innovaphone. Your filenames might be different according to your settings. The IP address of the app platform in the example is "172.16.13.100". Please change to the correct IP address of your setup.

Testing and expected behaviour

For testing the app we need to generate the HTTP requests. The command line tool CURL is a good choice for that. However you can use any tool that does the job. Let's take a look how this can be done and what's the expected behaviour on the network on the individual operations.

Storing the text using PUT

curl -i -X PUT http://172.16.13.100/newapp1/value -d "some text" -H "Content-Type: text/plain"

PUT /newapp1/value HTTP/1.1
Content-Type: text/plain
Content-Length: 9

some text

HTTP/1.1 200 OK
Content-Length: 0

Reading the text using GET

curl -i -X GET http://172.16.13.100/newapp1/value

GET /newapp1/value HTTP/1.1
HTTP/1.1 200 OK
Content-Length: 9
Content-Type: text/plain; charset=utf-8
some text

Deleting the text using DELETE

curl -i -X DELETE http://172.16.13.100/newapp1/value

DELETE /newapp1/value HTTP/1.1

HTTP/1.1 200 OK
Content-Length: 0

Step by step

Let's start with the unchanged app and understand what it does with incoming HTTP requests. For that we compile and run it from Visual Studio. Then we do a GET request.

curl -i -X GET http://172.16.13.100/newapp1/value

GET /newapp1/value HTTP/1.1

HTTP/1.1 307 Temporary Redirect
Location: ./13A000/value
...

The code that receives the GET and does the redirect can be found in NewApp1.cpp in the function NewApp1::WebserverPluginHttpListenResult. Let's add a debug printf to see what's going on and try again.

void NewApp1::WebserverPluginHttpListenResult(IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize)
{
    if (requestType == WS_REQUEST_GET) {
        debug->printf("GET %s", resourceName); // added this line
        if (plugin->BuildRedirect(resourceName, _BUILD_STRING_, strlen(_BUILD_STRING_))) {
            return;
        }
    }
    plugin->Cancel(WSP_CANCEL_NOT_FOUND);
}

After building and restarting the app we send another GET request. The console window of visual studio shows our trace.

06-14 15:28:13.351 GET /value

Explanations

Task 1: Create your own handler for HTTP requests

Let's start coding our REST API. We could do that by adding all the functionality to NewApp1::WebserverPluginHttpListenResult. But in this tutorial we want to create our own UWebserverPlugin class, that just handles the requests to our path /value.

Adding the PUT operation

Now we can start implementing the actual REST operations. For the text to be stored we first add a member char * value to our RestApi class.

class RestApi : public UWebserverPlugin {
public:
    RestApi();
    ~RestApi();
    void WebserverPluginHttpListenResult(IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize) override;

    char * value; // added this line
};

RestApi::RestApi()
{
    value = 0; // added this line
}

RestApi::~RestApi()
{
    if (value) free(value); // added this line
}

Then we can implement our PUT operation that stores the text. For that we need to create another class of type UWebserverPut.

#define DATA_SIZE_MAX 63

class RestApiPut : public UWebserverPut {
public:
    RestApiPut(class RestApi * rest);
    ~RestApiPut();
    void WebserverPutRequestAcceptComplete(IWebserverPut * const webserverPut) override;
    void WebserverPutRecvResult(IWebserverPut * const webserverPut, void * buffer, size_t len) override;
    void WebserverPutRecvCanceled(IWebserverPut * const webserverPut, void * buffer) override;
    void WebserverPutSendResult(IWebserverPut * const webserverPut) override;
    void WebserverPutCloseComplete(IWebserverPut * const webserverPut) override;

    class RestApi * rest;
    char data[DATA_SIZE_MAX + 1];
};

RestApiPut::RestApiPut(class RestApi * rest)
{
    this->rest = rest;
}

RestApiPut::~RestApiPut()
{
}

void RestApiPut::WebserverPutRequestAcceptComplete(IWebserverPut * const webserverPut)
{
    // start receiving data
    webserverPut->Recv(data, DATA_SIZE_MAX);
}

void  RestApiPut::WebserverPutRecvResult(IWebserverPut * const webserverPut, void * buffer, size_t len)
{
    // store received value in REST API
    data[len] = 0;
    if (rest->value) free(rest->value);
    rest->value = len ? _strdup(data) : nullptr;

    // send 202 OK response with no content data
    webserverPut->SetResultCode(WEBDAV_RESULT_OK, 0);
    webserverPut->Send(0, 0);
}

void  RestApiPut::WebserverPutRecvCanceled(IWebserverPut * const webserverPut, void * buffer)
{
}

void RestApiPut::WebserverPutSendResult(IWebserverPut * const webserverPut)
{
    // response is sent, close
    webserverPut->Close();
}

void RestApiPut::WebserverPutCloseComplete(IWebserverPut * const webserverPut)
{
    // clean-up
    delete webserverPut;
    delete this;
}

Additionally we need to use our RestApiPut class to handle incoming PUT requests. Therefore we change our RestApi::WebserverPluginHttpListenResult like follows.

void RestApi::WebserverPluginHttpListenResult(IWebserverPlugin * plugin, ws_request_type_t requestType, char * resourceName, const char * registeredPathForRequest, ulong64 dataSize)
{
    if (requestType == WS_REQUEST_GET) {
        debug->printf("RestApi GET");
        plugin->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else if (requestType == WS_REQUEST_PUT) {
        debug->printf("RestApi PUT");
        plugin->Accept(new RestApiPut(this));
    }
    else if (requestType == WS_REQUEST_DELETE) {
        debug->printf("RestApi DELETE");
        plugin->Cancel(WSP_CANCEL_NOT_FOUND);
    }
    else {
        plugin->Cancel(WSP_CANCEL_NOT_FOUND);
    }
}

Task 2: Adding the GET operation

Now we need a way to read the stored values using GET requests.

Task 3: Adding the DELETE operation

Now only the DELETE operation is missing.

Conclusion

Useful documentation