This repository has been archived by the owner on Nov 25, 2020. It is now read-only.
/
Response.php
360 lines (331 loc) · 8.9 KB
/
Response.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
<?php
/**
* Contains the T_Response class.
*
* @package controllers
* @author Rob Tuley
* @version SVN: $Id$
* @license http://knotwerk.com/licence MIT
*/
/**
* Request Response.
*
* This class encapsulates the reponse to any request. For HTTP for example, it
* encapsulates page headers, content sending, etc. Every response has the
* capability to use intercepting filters that can be attached at any time.
*
* @package controllers
*/
class T_Response extends Exception
{
/**
* Array of filter objects.
*
* @var array
*/
protected $filter = array();
/**
* Whether the response has been aborted.
*
* @var bool
*/
protected $is_aborted = false;
/**
* Status code.
*
* @var int
*/
protected $status = 200;
/**
* Content of response.
*
* @var array
*/
protected $content = null;
/**
* Response content type.
*
* @var string
*/
protected $contentType = 'text/html';
/**
* Character encoding of the (sent) request.
*
* @var string
*/
protected $encoding = T_CHARSET;
/**
* Response protocol.
*
* @var string
*/
protected $protocol = 'HTTP/1.1';
/**
* Response additional headers.
*
* @var array
*/
protected $headers = array();
/**
* Initialise response, default empty content 200 (OK).
*
* @param int $status status code of response
* @param string $content content of the response
*/
function __construct($status=200)
{
parent::__construct();
$this->status = $status;
if (isset($_SERVER['SERVER_PROTOCOL'])) {
$this->protocol = $_SERVER['SERVER_PROTOCOL'];
}
}
/**
* Append a filter to the end of the current filter chain.
*
* @param T_Response_Filter $filter filter to apply
* @param mixed $key optional filter access key
*/
function appendFilter(T_Response_Filter $filter,$key=null)
{
if (!is_null($key) && !array_key_exists($key,$this->filter)) {
$this->filter[$key] = $filter;
} elseif (is_null($key)) {
$this->filter[] = $filter;
} else {
throw new InvalidArgumentException("existing key $key");
}
/* filter is stored BEFORE pre-filtering in case a redirect is thrown in
the prefilter process. */
$filter->preFilter($this);
}
/**
* Get the reference to a particular intercepting filter object.
*
* @param mixed $key access key
* @return T_Response_Filter filter instance
*/
function filter($key)
{
if (array_key_exists($key,$this->filter)) {
return $this->filter[$key];
} else {
throw new InvalidArgumentException("key $key doesn't exist");
}
}
/**
* Apply prepare filters.
*
* @return T_Response fluent interface
*/
protected function prepareFilters()
{
foreach ($this->filter as $f) {
$f->prepareFilter($this);
}
return $this;
}
/**
* Apply post filters in reverse order.
*
* @return T_Response fluent interface
*/
function closeFilters()
{
$filters = array_reverse($this->filter);
foreach ($filters as $f) {
$f->postFilter($this);
}
return $this;
}
/**
* Abort the response (must be caled before creating a new response).
*/
function abort()
{
$filters = array_reverse($this->filter);
foreach ($filters as $f) {
$f->abortFilter($this);
}
$this->is_aborted = true;
}
/**
* Whether the response has been aborted.
*
* @return bool
*/
function isAborted() {
return $this->is_aborted;
}
/**
* Add a header key:value pair.
*
* Each header is sent as "key : value" on response send. This function
* automatically detects changes to encoding and content type and modifies
* the internal variables storing these values.
*
* @param string $key header key (e.g. 'Content-Type','Location')
* @param string $value header value
*/
function setHeader($key,$value)
{
$key = _transform($key,new T_Filter_HeaderKey('mb_trim'));
// detect content-type (optionally with encoding)
if (strcasecmp($key,'Content-Type')===0) {
$charset = new T_Pattern_Regex('/^(.*);\s?charset\s?=\s?([^\s]+)$/');
if ($match = $charset->getFirstMatch($value)) {
$this->contentType = $match[1];
$this->encoding = $match[2];
} else {
$this->contentType = $value;
}
} else {
$this->headers[$key] = $value;
}
}
/**
* Set the page status.
*
* @param int $status response status
*/
function setStatus($status)
{
$this->status = $status;
}
/**
* Get the page status.
*
* @return int page status code
*/
function getStatus()
{
return $this->status;
}
/**
* Set the character encoding.
*
* @param string $encoding character encoding
*/
function setEncoding($encoding)
{
$this->encoding = $encoding;
}
/**
* Get content.
*
* @return mixed $content content string or view
*/
function getContent()
{
return $this->content;
}
/**
* Set content.
*
* This function discards all previously set content.
*
* @param mixed $content content string or view
*/
function setContent($content)
{
$this->content = $content;
}
/**
* Returns the HTTP status message.
*
* @param int $code status code
* @return string status message
*/
protected function getStatusMsg($code)
{
switch ($code) {
case 201 : return 'Created';
case 202 : return 'Accepted';
case 204 : return 'No Content';
case 206 : return 'Partial Content';
case 304 : return 'Not Modified';
case 400 : return 'Bad Request';
case 401 : return 'Unauthorized';
case 403 : return 'Forbidden';
case 404 : return 'Not Found';
case 405 : return 'Method Not Allowed';
case 406 : return 'Not Acceptable';
case 410 : return 'Gone';
case 412 : return 'Precondition Failed';
case 500 : return 'Internal Server Error';
case 501 : return 'Not Implemented';
case 502 : return 'Bad Gateway';
case 503 : return 'Service Unavailable';
case 504 : return 'Gateway Timeout';
default : return '';
}
}
/**
* Sends a single header.
*
* @param string $header header string to send
*/
protected function sendHeader($header)
{
header($header);
}
/**
* Send the response status.
*
* The way the status is sent depends on the mode in which PHP is running.
* The standard way is to send the something like 'HTTP/1.1 404 Not Found',
* however in CGI mode PHP doesn't sent the response correctly (bug #27345)
* and instead the status must be sent as 'Status: 404 Not found'.
*/
protected function sendStatus()
{
$statusmsg = $this->getStatusMsg($this->status);
if (strlen($statusmsg)==0 || strpos(php_sapi_name(),'cgi')===false) {
$this->sendHeader( $this->protocol.' '.$this->status.
($statusmsg ? ' '.$statusmsg : '') );
} else {
$this->sendHeader('Status: '.$this->status.' '.$statusmsg);
}
}
/**
* Send the response headers.
*/
protected function sendAllHeaders()
{
$ini = 'Content-Type:'.$this->contentType.'; charset='.$this->encoding;
$this->sendHeader($ini);
foreach ($this->headers as $key => $value) {
$this->sendHeader($key . ': ' . $value);
}
}
/**
* Send the response body.
*/
protected function sendBody()
{
ob_start();
// ^ e.g. session headers added, issued as part of content render.
if (strcasecmp($this->encoding,T_CHARSET)!=0) {
throw new Exception('Different encoding not implemented yet.');
} else {
if (interface_exists('T_View')
&& ($this->content instanceof T_View)) { // from views
$this->content->toBuffer();
} else {
echo $this->content;
}
}
ob_end_flush();
}
/**
* Send the entire response.
*/
function send()
{
$this->prepareFilters();
$this->sendStatus();
$this->sendAllHeaders();
$this->sendBody();
$this->closeFilters();
}
}