ooRexx logo xhr.cls
#!/usr/bin/rexx
/* --------------------------------------------------------------------------------------- */
/* No freakin' copyright, no stinkin' license, no guarantees or warrantees                 */
/* (implied, explicit or whatever). Usage is totally and completely at your own risk.      */
/* Please keep this comment block as is if modifying this code. Thanks in advance,         */
/*   Ruurd Idenburg                                                                        */
/* --------------------------------------------------------------------------------------- */
::class xhr public
-- using attributes without‘expose’, so that methods are free in choosing variable names
::attribute host get
::attribute host set private
::attribute port get
::attribute port set private
::attribute socket get
::attribute socket set private
::attribute reqHeaders get
::attribute reqHeaders set private
::attribute hostname get
::attribute hostname set private
::attribute rspHeaders get
::attribute rspHeaders set private
::attribute response get
::attribute response set private

::method init
        use strict arg host, port=80
        self~hostname = host
        self~port = port
        self~host = .inetaddress~new(host,port)
        self~rspHeaders = .array~new
exit

::method doHeaders private
        self~reqHeaders = .array~new
        self~addHeader('Host:' self~hostname)
        self~addHeader('User-Agent: Not My Browser 0.9')
        self~addHeader('Accept: */*')
exit

::method get -- HTTP/1.1 GET with support for Content-Length: or Transfer-Encoding: chunked
        use strict arg query
        self~socket = .socket~new
        if self~socket~connect(self~host)<0 then do
                raise syntax 48.900 array('Socket connection to' self~hostname 'on port' self~port 'failed')
        end
        self~doHeaders
        request = 'GET' query 'HTTP/1.1' || '0d0a'x || self~reqHeaders~makeString(,'0d0a'x) || '0d0a0d0a'x
        if self~socket~send(request)<0 then do
                raise syntax 48.900 array('Sending request to' self~hostname 'on port' self~port 'failed')
        end
        recvLength = 1024
        response = self~socket~recv(recvLength)
        if response==.nil then do
                if self~socket~errno<0 then do
                        raise syntax 48.900 array('Receiving response from' self~hostname 'on port' self~port 'failed')
                end
                else do
                        raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
                end
        end
        parse var response headers '0d0a0d0a'x data
        parse var headers . 'Content-Length:' length '0d0a'x .
        if length=='' then do
                parse var headers . 'Transfer-Encoding:' how '0d0a'x .
                if how='chunked' then do
                        data = self~getChunked(data)
                end
                else do
                        raise syntax 48.900 array('Unsupported Transfer-Encoding:' how)
                end
        end
        else do (length>data~length) -- if we need to receive more content
                data = self~getLength(data,length)
        end
        self~rspHeaders = headers~makeArray('0d0a'x)
        self~response = data
        if self~socket~close<0 then do
                raise syntax 48.900 array('Closing socket for' self~hostname 'on port' self~port 'failed')
        end
exit
       
::method post -- HTTP/1.1 POST, for now copied mostly from GET; will do finetuning/combining later on
        use strict arg target,content
        self~socket = .socket~new
        if self~socket~connect(self~host)<0 then do
                raise syntax 48.900 array('Socket connection to' self~hostname 'on port' self~port 'failed')
        end
        self~doHeaders
        self~addHeader('Content-Type: application/x-www-form-urlencoded')
        self~addHeader('Content-Length:' content~length)
        request = 'POST' target 'HTTP/1.1' || '0d0a'x || self~reqHeaders~makeString(,'0d0a'x) || '0d0a0d0a'x || content
        if self~socket~send(request)<0 then do
                raise syntax 48.900 array('Sending request to' self~hostname 'on port' self~port 'failed')
        end
        recvLength = 1024
        response = self~socket~recv(recvLength)
        if response==.nil then do
                if self~socket~errno<0 then do
                        raise syntax 48.900 array('Receiving response from' self~hostname 'on port' self~port 'failed')
                end
                else do
                        raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
                end
        end
        parse var response headers '0d0a0d0a'x data
        parse var headers . 'Content-Length:' length '0d0a'x .
        if length=='' then do
                parse var headers . 'Transfer-Encoding:' how '0d0a'x .
                if how='chunked' then do
                        data = self~getChunked(data)
                end
                else do
                        raise syntax 48.900 array('Unsupported Transfer-Encoding:' how)
                end
        end
        else do (length>data~length) -- if we need to receive more content
                data = self~getLength(data,length)
        end
        self~rspHeaders = headers~makeArray('0d0a'x)
        self~response = data
        if self~socket~close<0 then do
                raise syntax 48.900 array('Closing socket for' self~hostname 'on port' self~port 'failed')
        end
exit
       
::method addHeader      -- Allow for user supplied request headers
        use strict arg header
        self~reqHeaders~append(header)
exit
       
::method getChunked private
        use strict arg chunks
        -- The end of content in chunked mode is identified by the last chunk ('0' || '0d0a0d0a'x )
  lastChunk = .false
  if chunks<>"" then do
    lastChunk = (chunks~substr(chunks~length-4)==(0 || '0d0a0d0a'x))
  end
        do while \lastChunk
                nextChunk = self~socket~recv(1024)
                if nextChunk==.nil then do
                        if self~socket~errno<0 then do
                                raise syntax 48.900 array('Receiving response chunk from' self~hostname 'on port' self~port 'failed')
                        end
                        else do
                                raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
                        end
                end
                chunks ||= nextChunk
                lastChunk = (chunks~substr(chunks~length-4)==(0 || '0d0a0d0a'x))
        end
        data = ''
        do until chunks==(0 || '0d0a0d0a'x)
                parse var chunks header '0d0a'x chunk '0d0a'x chunks
                data ||= chunk
        end
        return data
exit
       
::method getLength private
        use strict arg content,length
        recvLength = length-content~length -- already received part of the content
        do while recvLength>0
                nextContent = self~socket~recv(recvLength)
                if nextContent==.nil then do
                        if self~socket~errno<0 then do
                                raise syntax 48.900 array('Receiving response chunk from' self~hostname 'on port' self~port 'failed')
                        end
                        else do
                                raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
                        end
                end
                content ||= nextContent
                recvLength -= nextContent~length
        end
        return content
exit
       
::requires 'socket.cls'
If you feel inclined to make corrections, suggestions etc., please mail me any.
All content © Ruurd Idenburg, 2007–2018, except where marked otherwise. All rights reserved. This page is primarily for non-commercial use only. The Idenburg website records no personal information and sets no ‘cookies’. This site is hosted on a VPS(Virtual Private System) rented from CloudVPS, a Dutch company, falling under Dutch (privacy) laws.

This page updated on Thu, 28 Apr 2016 14:59:56 +0200 by Ruurd Idenburg.