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'