ooRexx logo
   1: #!/usr/bin/rexx
   2: /* --------------------------------------------------------------------------------------- */
   3: /* No freakin' copyright, no stinkin' license, no guarantees or warrantees                 */
   4: /* (implied, explicit or whatever). Usage is totally and completely at your own risk.      */
   5: /* Please keep this comment block as is if modifying this code. Thanks in advance,         */
   6: /*   Ruurd Idenburg                                                                        */
   7: /* --------------------------------------------------------------------------------------- */
   8: ::class xhr public
   9: -- using attributes without‘expose’, so that methods are free in choosing variable names
  10: ::attribute host get
  11: ::attribute host set private
  12: ::attribute port get
  13: ::attribute port set private
  14: ::attribute socket get
  15: ::attribute socket set private
  16: ::attribute reqHeaders get
  17: ::attribute reqHeaders set private
  18: ::attribute hostname get
  19: ::attribute hostname set private
  20: ::attribute rspHeaders get
  21: ::attribute rspHeaders set private
  22: ::attribute response get
  23: ::attribute response set private
  24: 
  25: ::method init
  26: 	use strict arg host, port=80
  27: 	self~hostname = host
  28: 	self~port = port
  29: 	self~host = .inetaddress~new(host,port)
  30: 	self~rspHeaders = .array~new
  31: exit 
  32: 
  33: ::method doHeaders private
  34: 	self~reqHeaders = .array~new
  35: 	self~addHeader('Host:' self~hostname)
  36: 	self~addHeader('User-Agent: Not My Browser 0.9')
  37: 	self~addHeader('Accept: */*')
  38: exit
  39: 
  40: ::method get -- HTTP/1.1 GET with support for Content-Length: or Transfer-Encoding: chunked 
  41: 	use strict arg query
  42: 	self~socket = .socket~new
  43: 	if self~socket~connect(self~host)<0 then do
  44: 		raise syntax 48.900 array('Socket connection to' self~hostname 'on port' self~port 'failed')
  45: 	end
  46: 	self~doHeaders
  47: 	request = 'GET' query 'HTTP/1.1' || '0d0a'x || self~reqHeaders~makeString(,'0d0a'x) || '0d0a0d0a'x
  48: 	if self~socket~send(request)<0 then do
  49: 		raise syntax 48.900 array('Sending request to' self~hostname 'on port' self~port 'failed')
  50: 	end
  51: 	recvLength = 1024
  52: 	response = self~socket~recv(recvLength)
  53: 	if response==.nil then do
  54: 		if self~socket~errno<0 then do
  55: 			raise syntax 48.900 array('Receiving response from' self~hostname 'on port' self~port 'failed')
  56: 		end
  57: 		else do
  58: 			raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
  59: 		end
  60: 	end
  61: 	parse var response headers '0d0a0d0a'x data
  62: 	parse var headers . 'Content-Length:' length '0d0a'x .
  63: 	if length=='' then do
  64: 		parse var headers . 'Transfer-Encoding:' how '0d0a'x .
  65: 		if how='chunked' then do
  66: 			data = self~getChunked(data)
  67: 		end
  68: 		else do
  69: 			raise syntax 48.900 array('Unsupported Transfer-Encoding:' how)
  70: 		end
  71: 	end
  72: 	else do (length>data~length) -- if we need to receive more content
  73: 		data = self~getLength(data,length) 
  74: 	end
  75: 	self~rspHeaders = headers~makeArray('0d0a'x)
  76: 	self~response = data
  77: 	if self~socket~close<0 then do
  78: 		raise syntax 48.900 array('Closing socket for' self~hostname 'on port' self~port 'failed')
  79: 	end
  80: exit
  81: 	
  82: ::method post -- HTTP/1.1 POST, for now copied mostly from GET; will do finetuning/combining later on 
  83: 	use strict arg target,content
  84: 	self~socket = .socket~new
  85: 	if self~socket~connect(self~host)<0 then do
  86: 		raise syntax 48.900 array('Socket connection to' self~hostname 'on port' self~port 'failed')
  87: 	end
  88: 	self~doHeaders
  89: 	self~addHeader('Content-Type: application/x-www-form-urlencoded')
  90: 	self~addHeader('Content-Length:' content~length)
  91: 	request = 'POST' target 'HTTP/1.1' || '0d0a'x || self~reqHeaders~makeString(,'0d0a'x) || '0d0a0d0a'x || content
  92: 	if self~socket~send(request)<0 then do
  93: 		raise syntax 48.900 array('Sending request to' self~hostname 'on port' self~port 'failed')
  94: 	end
  95: 	recvLength = 1024
  96: 	response = self~socket~recv(recvLength)
  97: 	if response==.nil then do
  98: 		if self~socket~errno<0 then do
  99: 			raise syntax 48.900 array('Receiving response from' self~hostname 'on port' self~port 'failed')
 100: 		end
 101: 		else do
 102: 			raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
 103: 		end
 104: 	end
 105: 	parse var response headers '0d0a0d0a'x data
 106: 	parse var headers . 'Content-Length:' length '0d0a'x .
 107: 	if length=='' then do
 108: 		parse var headers . 'Transfer-Encoding:' how '0d0a'x .
 109: 		if how='chunked' then do
 110: 			data = self~getChunked(data)
 111: 		end
 112: 		else do
 113: 			raise syntax 48.900 array('Unsupported Transfer-Encoding:' how)
 114: 		end
 115: 	end
 116: 	else do (length>data~length) -- if we need to receive more content
 117: 		data = self~getLength(data,length) 
 118: 	end
 119: 	self~rspHeaders = headers~makeArray('0d0a'x)
 120: 	self~response = data
 121: 	if self~socket~close<0 then do
 122: 		raise syntax 48.900 array('Closing socket for' self~hostname 'on port' self~port 'failed')
 123: 	end
 124: exit
 125: 	
 126: ::method addHeader	-- Allow for user supplied request headers
 127: 	use strict arg header
 128: 	self~reqHeaders~append(header)
 129: exit
 130: 	
 131: ::method getChunked private
 132: 	use strict arg chunks
 133: 	-- The end of content in chunked mode is identified by the last chunk ('0' || '0d0a0d0a'x )
 134:   lastChunk = .false
 135:   if chunks<>"" then do
 136:     lastChunk = (chunks~substr(chunks~length-4)==(0 || '0d0a0d0a'x))
 137:   end
 138: 	do while \lastChunk
 139: 		nextChunk = self~socket~recv(1024)
 140: 		if nextChunk==.nil then do
 141: 			if self~socket~errno<0 then do
 142: 				raise syntax 48.900 array('Receiving response chunk from' self~hostname 'on port' self~port 'failed')
 143: 			end
 144: 			else do
 145: 				raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
 146: 			end
 147: 		end
 148: 		chunks ||= nextChunk
 149: 		lastChunk = (chunks~substr(chunks~length-4)==(0 || '0d0a0d0a'x))
 150: 	end
 151: 	data = ''
 152: 	do until chunks==(0 || '0d0a0d0a'x)
 153: 		parse var chunks header '0d0a'x chunk '0d0a'x chunks
 154: 		data ||= chunk
 155: 	end
 156: 	return data
 157: exit
 158: 	
 159: ::method getLength private
 160: 	use strict arg content,length
 161: 	recvLength = length-content~length -- already received part of the content
 162: 	do while recvLength>0
 163: 		nextContent = self~socket~recv(recvLength)
 164: 		if nextContent==.nil then do
 165: 			if self~socket~errno<0 then do
 166: 				raise syntax 48.900 array('Receiving response chunk from' self~hostname 'on port' self~port 'failed')
 167: 			end
 168: 			else do
 169: 				raise syntax 48.900 array('Socket on' self~hostname 'on port' self~port 'already closed')
 170: 			end
 171: 		end
 172: 		content ||= nextContent
 173: 		recvLength -= nextContent~length
 174: 	end
 175: 	return content 
 176: exit
 177: 	
 178: ::requires 'socket.cls' 
All content © Ruurd Idenburg, 2007–2025, 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 my on server at my home, falling under Dutch (privacy) laws.

This page updated on Wed, 28 May 2025 10:38:18 +0200.