Using HTTPLib

In this chapter we will learn how to use the HTTPLib library.

Introduction

This extension provides support for the httplib library

URL: https://github.com/yhirose/cpp-httplib

Server Class Methods

  • route(cType,cURL,cCode)

  • setContent(cContent,cType)

  • setHTMLPage(oPage)

  • shareFolder(cFolder)

  • setCookie(cStr)

  • cookies() -> aList

  • getFileContent(cFile) -> cString

  • getFileName(cFile) -> cString

  • request().body() -> cString

  • setStatus(nStatusCode)

  • getStatus() -> nStatusCode

Example

load "httplib.ring"

oServer = new Server {

        ? "Try localhost:8080/hi"
        route(:Get,"/hi",:mytest)

        ? "Listen to port 8080"
        listen("0.0.0.0", 8080)

}

func mytest
        oServer.setContent("Hello World!", "text/plain")

Samples

The samples exist in ring/samples/UsingHTTPLib folder

Printing Constants

The next example print the constants defined by the extension

load "httplib.ring"

? "Constants:"

? HTTPLIB_KEEPALIVE_TIMEOUT_SECOND
? HTTPLIB_KEEPALIVE_MAX_COUNT
? HTTPLIB_CONNECTION_TIMEOUT_SECOND
? HTTPLIB_CONNECTION_TIMEOUT_USECOND
? HTTPLIB_READ_TIMEOUT_SECOND
? HTTPLIB_READ_TIMEOUT_USECOND
? HTTPLIB_WRITE_TIMEOUT_SECOND
? HTTPLIB_WRITE_TIMEOUT_USECOND
? HTTPLIB_IDLE_INTERVAL_SECOND
? HTTPLIB_IDLE_INTERVAL_USECOND
? HTTPLIB_REQUEST_URI_MAX_LENGTH
? HTTPLIB_REDIRECT_MAX_COUNT
? HTTPLIB_PAYLOAD_MAX_LENGTH
? HTTPLIB_TCP_NODELAY
? HTTPLIB_COMPRESSION_BUFSIZ
? HTTPLIB_THREAD_POOL_COUNT
? HTTPLIB_RECV_FLAGS
? HTTPLIB_LISTEN_BACKLOG

Using HTTP GET

Example(1):

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/hi"
oServer.route(:Get,"/hi",:mytest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func mytest
        oServer.setContent("Hello World!", "text/plain")

Example(2):

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/one"
oServer.route(:Get,"/one",:one)
? "Try localhost:8080/two"
oServer.route(:Get,"/two",:two)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func one
        oServer.setContent("one", "text/plain")

func two
        oServer.setContent("two", "text/plain")

Example(3):

In this example we will use anonymous function

load "httplib.ring"

? "Try localhost:8080/hello"

oServer = new Server {

        route(:Get,"/hello",func {
                oServer.setContent("Hello, World!", "text/plain")
        })

        listen("0.0.0.0", 8080)

}

Example(4):

load "httplib.ring"

? "Try localhost:8080/hi - See output in console at Server-Side"
? "Try localhost:8080/hello - See output in web browser at Client-Side"

oServer = new Server {

        route(:Get,"/hi",'? "Wow, I love Ring programming!"')
        route(:Get,"/hello",'oServer.setContent("Hello, World!", "text/plain")')

        listen("0.0.0.0", 8080)

}

Example(5):

load "httplib.ring"

new Client("localhost:8080") {
        ? download("/one")
        ? download("/two")
}

Tip

Using the Download() method in the InternetLib is faster

Using WebLib

Example(1):

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/report"
oServer.route(:Get,"/report",:report)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func report
        oPage = New HTMLPage
        {
                nRowsCount = 10
                title = "Report"
                h1 { text("Customers Report") }
                Table
                {
                        style = stylewidth("100%") + stylegradient(4)
                        TR
                        {
                                TD { WIDTH="10%" text("Customers Count : " )  }
                                                        TD { text (nRowsCount) }
                        }
                }
                Table
                {
                        style = stylewidth("100%") + stylegradient(26)
                        TR
                        {
                                style = stylewidth("100%") + stylegradient(24)
                                TD { text("Name " )  }
                                TD { text("Age" ) }
                                TD { text("Country" ) }
                                TD { text("Job" ) }
                                TD { text("Company" ) }
                        }
                        for x =  1 to nRowsCount
                                TR
                                {
                                        TD { text("Test" )  }
                                        TD { text("30" ) }
                                        TD { text("Egypt" ) }
                                        TD { text("Sales" ) }
                                        TD { text("Future" ) }
                                }
                        next
                }
        }

        oServer.setHTMLPage(oPage)

Using HTTP Post

Example(1):

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/form"
oServer.route(:Get,"/form",:form)
oServer.route(:Post,"/formresponse",:formresponse)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func form

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Post Test")
                        newline()
                boxend()
                divstart([:style=StyleFloatLeft()+StyleWidth("100px") ])
                        newline()
                        text( "Number1 : " )    newline() newline()
                        text( "Number2 : " )    newline() newline()
                divend()
                formpost("formresponse")
                        divstart([ :style = styleFloatLeft()+StyleWidth("200px") ])
                                newline()
                                textbox([ :name = "Number1" ])  newline() newline()
                                textbox([ :name = "Number2" ])  newline() newline()
                                submit([ :value = "Send" ] )
                        divend()
                formend()
        }
        oServer.setHTMLPage(oPage)

func formresponse

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Post Result" )
                        newline()
                boxend()
                divstart([ :style = styleFloatLeft()+styleWidth("200px") ])
                        newline()
                        text( "Number1 : " + oServer["Number1"] )
                        newline() newline()
                        text( "Number2 : " + oServer["Number2"] )
                        newline() newline()
                        text( "Sum : " + (0 + oServer["Number1"] +
                                        oServer["Number2"] ) )
                        newline()
                divend()
        }

        oServer.setHTMLPage(oPage)

Example(2):

load "httplib.ring"
Load "openssllib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/hash"
oServer.route(:Get,"/hash",:hash)
oServer.route(:Post,"/hashresponse",:hashresponse)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func hash

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Hash Test")
                        newline()
                boxend()
                divstart([:style = StyleFloatLeft() + StyleWidth("100px") ])
                        newline()
                        text( "Value : " )
                        newline() newline()
                divend()
                formpost("/hashresponse")
                        divstart([:style = StyleFloatLeft() + StyleWidth("300px") ])
                                newline()
                                textbox([ :name = "Value" ])
                                newline() newline()
                                submit([ :value = "Send" ])
                        divend()
                formend()
        }
        oServer.setHTMLPage(oPage)

func hashresponse

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Hash Result" )
                        newline()
                boxend()
                divstart([:style = styleFloatLeft() + styleWidth("100%") ])
                        newline()
                        text( "Value : " + oServer["Value"] )
                        newline()
                        text( "MD5 : " + md5(oServer["Value"]) )
                        newline()
                        text( "SHA1 : " + SHA1(oServer["Value"]) )
                        newline()
                        text( "SHA256 : " + SHA256(oServer["Value"]) )
                        newline()
                        text( "SHA224 : " + SHA224(oServer["Value"]) )
                        newline()
                        text( "SHA384 : " + SHA384(oServer["Value"]) )
                        newline()
                        text( "SHA512 : " + SHA512(oServer["Value"]) )
                        newline()
                divend()
        }

        oServer.setHTMLPage(oPage)

Getting the Request Body

When building APIs, it’s common to receive data, like JSON, in the raw body of a POST request. The oServer[“key”] syntax is for form-data, not raw bodies. To get the raw body, use the body() method on the request object.

Example: Receiving JSON Data

load "httplib.ring"
load "jsonlib.ring"

? "Start the server..."
oServer = new Server

? `Try: curl -X POST -H "Content-Type: application/json" -d '{"name": "Ring"}' http://localhost:8080/data`
oServer.route(:Post, "/data", :process_data)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func process_data
        // Get the request object
        oRequest = oServer.request()

        // Get the raw body as a string
        cBody = oRequest.body()

        ? "Received raw body: " + cBody

        // Now you can parse it (e.g., as JSON)
        aJson = json2list(cBody)
        cName = aJson["name"]

        oServer.setContent("Hello, " + cName + "!", "text/plain")

Using HTTP PUT

Example(1):

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/update"
? `Try: curl -X PUT -d "test data" http://localhost:8080/update`
oServer.route(:Put,"/update",:puttest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func puttest
        cBody = oServer.request().body()
        oServer.setContent("PUT Data received: " + cBody, "text/plain")

Example(2):

load "httplib.ring"
load "jsonlib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/items"
? `Try: curl -X PUT -H "Content-Type: application/json" -d '{"name": "Item 1"}' http://localhost:8080/items`
oServer.route(:Put,"/items",:updateitem)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func updateitem
        cBody = oServer.request().body()
        aJson = json2list(cBody)
        cName = aJson[:name]
        oServer.setContent("Updated item: " + cName, "text/plain")

Using HTTP PATCH

Example:

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/patch"
? `Try: curl -X PATCH -H "Content-Type: application/json" -d '{"name": "Partially Updated"}' http://localhost:8080/patch`
oServer.route(:Patch,"/patch",:patchtest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func patchtest
        cBody = oServer.request().body()
        oServer.setContent("PATCH Data received: " + cBody, "text/plain")

Using HTTP DELETE

Example(1):

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/delete"
? `Try: curl -X DELETE http://localhost:8080/delete`
oServer.route(:Delete,"/delete",:deletetest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func deletetest
        oServer.setContent("Item deleted", "text/plain")

Example(2):

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/items/5"
? "Example: localhost:8080/items/123"
? `Try: curl -X DELETE http://localhost:8080/items/5`
oServer.route(:Delete,"/items/(\d+)",:deleteitem)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func deleteitem
        cItemId = oServer.Match(1)
        oServer.setContent("Deleted item: " + cItemId, "text/plain")

Using HTTP OPTIONS

Example:

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/options"
? `Try: curl -v -X OPTIONS http://localhost:8080/options`
oServer.route(:Options,"/options",:optionstest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func optionstest
        oServer.setContent("Allowed: GET, POST, PUT, PATCH, DELETE", "text/plain")
        oServer.response().set_header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS")

REST API Authentication

A common way to secure a REST API is by requiring an API key sent in an HTTP header. The setStatus() method is useful here to send the correct HTTP status codes, like 401 Unauthorized for failed authentication or 200 OK for success.

Example:

load "httplib.ring"
load "jsonlib.ring"

# A simple list of valid API keys.
aValidApiKeys = [
        "secret-key-123",
        "power-user-456",
        "limited-access-789"
]

# Create the main server object
oServer = new Server {

        # Define the protected API route
        route(:Get, "/api/data", func() {

                # Authentication check
                if isAuthenticated(oServer.request())
                        # Success: Client is Authenticated
                        ? "Request received with valid API Key. Sending data."

                        # Prepare the JSON data response
                        aJsonData = [
                                :status = "success",
                                :message = "Welcome, authenticated user!",
                                :data = [
                                        :user_id = 12345,
                                        :permissions = ["read_data", "view_reports"]
                                ]
                        ]
                        cJsonResponse = list2json(aJsonData)

                        # Send the 200 OK response with the JSON data
                        oServer.setStatus(200)
                        ? "Status for '/api/data' route is: " + oServer.getStatus()
                        oServer.setContent(cJsonResponse, "application/json")

                else
                        # Failure: Client is Not Authenticated
                        ? "Request received with missing or invalid API Key. Denying access."

                        # Prepare the JSON error response
                        aErrorData = [
                                :error = "Unauthorized",
                                :message = "A valid 'X-API-KEY' header is required to access this resource."
                        ]
                        cErrorResponse = list2json(aErrorData)

                        # Send the 401 Unauthorized response
                        oServer.setStatus(401)
                        ? "Status for '/api/data' route is: " + oServer.getStatus()
                        oServer.setContent(cErrorResponse, "application/json")
                ok
        })

        ? "REST API Server listening at http://localhost:8080"
        ? "Try accessing the protected route '/api/data' with and without an API key."
        ? "Try: curl -H 'X-API-KEY: secret-key-123' http://localhost:8080/api/data"
        ? "Or without a key: curl -v http://localhost:8080/api/data"
        listen("0.0.0.0", 8080)
}

# Helper function to check for a valid API key in the request headers
func isAuthenticated oRequest
        # Check if the 'X-API-KEY' header is present
        if not oRequest.has_header("X-API-KEY")
                return false
        ok

        # Get the key provided by the client
        cClientKey = oRequest.get_header_value("X-API-KEY")

        # Check if the client's key exists in our list of valid keys
        if find(aValidApiKeys, cClientKey)
                return true
        else
                return false
        ok

More Samples

Using Gradients:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/gradient"
oServer.route(:Get,"/gradient",:gradient)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func gradient

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text("StyleGradient() Function")
                boxend()
                for x = 1 to 60
                        divstart([ :id = x , :align = "center" ,
                                   :style = stylefloatleft() +
                                                        stylesize(string(100/60*6)+"%","50px") +
                                                        stylegradient(x) ])
                                h3(x)
                        divend()
                next
        }
        oServer.setHTMLPage(oPage)

Using Lists:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/lists"
oServer.route(:Get,"/lists",:lists)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func lists

        oPage = New HTMLPageFunctions
        {
                ulstart([])
                        for x = 1 to 10
                                listart([])
                                        text(x)
                                liend()
                        next
                ulend()

                list2ul(["one","two","three","four","five"])

                ulstart([])
                        for x = 1 to 10
                                listart([])
                                        cFuncName = "btn"+x+"()"
                                        button([ :onclick = cFuncName , :value = x])
                                        script(scriptfuncalert(cFuncName,string(x)))
                                liend()
                        next
                ulend()
        }
        oServer.setHTMLPage(oPage)

Using Tables:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/table"
oServer.route(:Get,"/table",:table)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func table

        oPage = New HTMLPageFunctions
        {
                divstart([ :style = styledivcenter("400px","500px") ] )
                        style(styletable() + styletablerows("t01"))
                        tablestart([ :id = :t01 , :style = stylewidth("100%") ])
                                rowstart([])
                                        headerstart([]) text("Number") headerend()
                                        headerstart([]) text("square") headerend()
                                rowend()
                                for x = 1 to 10
                                        rowstart([])
                                                cellstart([]) text(x) cellend()
                                                cellstart([]) text(x*x) cellend()
                                        rowend()
                                next
                        tableend()
                divend()
        }
        oServer.setHTMLPage(oPage)

Play Video:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/play"
oServer.route(:Get,"/play",:play)

? "We support files in the res folder like res/horse.ogg and res/movie.mp4"
oServer.shareFolder("res")

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func play

        oPage = New HTMLPage
        {
                Title = "Welcome"
                h1 { text("Play sound and video!") }
                div
                {
                        audio
                        {
                                src = "res/horse.ogg"
                                type = "audio/ogg"
                        }

                        video
                        {
                                width = 320
                                height = 240
                                src = "res/movie.mp4"
                                type = "video/mp4"
                        }

                }
        }
        oServer.setHTMLPage(oPage)

Using Cookies

Example:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/cookie"
oServer.route(:Get,"/cookie",:cookie)
oServer.route(:Get,"/cookieresponse",:cookieresponse)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func cookie

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Cookie Test" )
                        newline()
                boxend()
                link([ :url = "/cookieresponse", :title = "Use Cookies" ])
        }

        oServer.setCookie("programminglanguage=Ring")
        oServer.setCookie("library=HTTPLib")

        oServer.setHTMLPage(oPage)

func cookieresponse

        aCookies = oServer.Cookies()

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Cookies Values" )
                        newline()
                boxend()
                link([ :url = "cookie", :title = "back" ])
                newline()
                divstart([:style="float:left;width:200px"])
                        text( "Programming Language : " + aCookies[:programminglanguage] )
                        newline()
                        text( "Library : " + aCookies[:library] )
                        newline()
                divend()
        }
        oServer.setHTMLPage(oPage)

Uploading Files

Example:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

cUploadFolder = "upload/"
oServer.shareFolder(cUploadFolder)

? "Try localhost:8080/upload"
oServer.route(:Get,"/upload",:upload)
oServer.route(:Post,"/uploadresponse",:uploadresponse)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func upload

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Upload File" )
                        newline()
                boxend()
                for x = 1 to 3 newline() next
                formupload("/uploadresponse")
                        text( "Customer Name : " )
                        textbox([ :name = "custname" ])
                        newline() newline()
                        divstart([ :style = styleFloatLeft() + styleWidth("90px") ])
                                uploadfile("file1")  newline() newline()
                                uploadfile("file2") newline() newline()
                                submit([ :value = "Send" ])
                        divend()
                formend()
        }

        oServer.setHTMLPage(oPage)

func uploadresponse

        oPage = New HTMLPageFunctions
        {
                boxstart()
                        text( "Upload Result" )
                        newline()
                boxend()
                newline()
                divstart([ :style=  styleFloatLeft() + styleWidth("100px") ])
                        text( "Name : " + oServer["custname"] )
                        newline()
                divend()
                getuploadedfile(self,"file1")
                getuploadedfile(self,"file2")
        }

        oServer.setHTMLPage(oPage)


Func getUploadedFile oObj,cFile
        cNewFileName = oServer.getfilename(cFile)
        if cNewFileName = NULL return ok
        cNewFileContent = oServer.getFileContent(cFile)
        /*
                Here we use object.property instead of object { }
                To avoid executing braceend() method
        */
        cFileName = cUploadFolder + cNewFileName
        write(cFileName,cNewFileContent)
        if isLinux()
                system("chmod a+x "+cFileName)
        ok
        oObj.newline()
        oObj.text( "File "+cFileName+ " Uploaded ..." )
        oObj.newline()
        imageURL = cFileName
        oObj.link([ :url = imageURL, :title = "Download" ])
        oObj.newline()
        oObj.image( [ :url = imageURL , :alt = :image  ] )
        oObj.newline()

Using Templates

Example:

load "httplib.ring"
load "weblib.ring"
import System.Web

? "Start the server..."
oServer = new Server

? "Try localhost:8080/template"
oServer.route(:Get,"/template"," new numbersController { start() } ")

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

class numbersController

        MyHeader aNumbers

        func Start

                MyHeader = New Header
                {
                        cColumn1 = "Number" cColumn2 = "Square"
                }

                aNumbers = list(20)

                for x = 1 to len(aNumbers)
                        aNumbers[x] = new number
                        {
                                nValue  = x   nSquare = x*x
                        }
                next

                cTemp = Template("templates/mynumbers.html",self)

                oPage = new HTMLPageFunctions
                {
                        boxstart()
                                text( "Test Templates" )
                                newline()
                        boxend()
                        html(cTemp)
                }

                oServer.setHTMLPage(oPage)

Class Header cColumn1 cColumn2

Class Number nValue   nSquare

Regular Expressions

Example:

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/numbers/<number>"
? "Example: localhost:8080/numbers/123"
oServer.route(:Get,"(/numbers/(\d+))",:mytest)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func mytest
        cOutput = "Match(1): " + oServer.Match(1) + nl
        cOutput += "Match(2): " + oServer.Match(2) + nl
        oServer.setContent(cOutput, "text/plain")

Stop the Server

Example:

load "httplib.ring"

? "Start the server..."
oServer = new Server

? "Try localhost:8080/time"
? "Try localhost:8080/stop"
oServer.route(:Get,"/time",:gettime)
oServer.route(:Get,"/stop",:stop)

? "Listen to port 8080"
oServer.listen("0.0.0.0", 8080)

func gettime
        oServer.setContent("Time: " + time(), "text/plain")

func stop
        oServer.stop()