Gentics Portal.Node PHP API
 All Classes Namespaces Functions Variables Pages
http.inc.php
1 <?php
2 
3 /**
4  * Query an HTTP(S) URL with the given request parameters and return the
5  * response headers and status code. The socket is returned as well and
6  * will point to the begining of the response payload (after all headers
7  * have been read), and must be closed with fclose().
8  * @param $url the request URL
9  * @param $request the request method may optionally be overridden.
10  * @param $timeout connection and read timeout in seconds
11  */
12 
13 function http_request($request, $timeout = 5)
14 {
15  $url = $request['url'];
16 
17  // Extract the hostname from url
18  $parts = parse_url($url);
19  if (array_key_exists('host', $parts)) {
20  $remote = $parts['host'];
21  } else {
22  return myErrorHandler("url ($url) has no host. Is it relative?");
23  }
24  if (array_key_exists('port', $parts)) {
25  $port = $parts['port'];
26  } else {
27  $port = 0;
28  }
29 
30  // Beware that RFC2616 (HTTP/1.1) defines header fields as case-insensitive entities.
31  $request_headers = "";
32  foreach ($request['headers'] as $name => $value) {
33  switch (strtolower($name)) {
34  //omit some headers
35  case "keep-alive":
36  case "connection":
37  //TODO: we don't handle any compression encodings. compression
38  //can cause a problem if client communication is already being
39  //compressed by the server/app that integrates this script
40  //(which would double compress the content, once from the remote
41  //server to us, and once from us to the client, but the client
42  //would de-compress only once).
43  case "accept-encoding":
44  break;
45  // correct the host parameter
46  case "host":
47  $host_info = $remote;
48  if ($port) {
49  $host_info .= ':' . $port;
50  }
51  $request_headers .= "$name: $host_info\r\n";
52  break;
53  // forward all other headers
54  default:
55  $request_headers .= "$name: $value\r\n";
56  break;
57  }
58  }
59 
60  //set fsockopen transport scheme, and the default port
61  switch (strtolower($parts['scheme'])) {
62  case 'https':
63  $scheme = 'ssl://';
64  if ( ! $port ) $port = 443;
65  break;
66  case 'http':
67  $scheme = '';
68  if ( ! $port ) $port = 80;
69  break;
70  default:
71  //some other transports are available but not really supported
72  //by this script: http://php.net/manual/en/transports.inet.php
73  $scheme = $parts['scheme'] . '://';
74  if ( ! $port ) {
75  return myErrorHandler("Unknown scheme ($scheme) and no port.");
76  }
77  break;
78  }
79 
80  //we make the request with socket operations since we don't want to
81  //depend on the curl extension, and the higher level wrappers don't
82  //give us usable error information.
83 
84  $sock = @fsockopen("$scheme$remote", $port, $errno, $errstr, $timeout);
85  if ( ! $sock ) {
86  return myErrorHandler("Unable to open URL ($url): $errstr");
87  }
88 
89  //the timeout in fsockopen is only for the connection, the following
90  //is for reading the content
91  stream_set_timeout($sock, $timeout);
92 
93  //an absolute url should only be specified for proxy requests
94  if (array_key_exists('path', $parts)) {
95  $path_info = $parts['path'];
96  } else {
97  $path_info = '/';
98  }
99 
100  if (array_key_exists('query', $parts)) $path_info .= '?' . $parts['query'];
101  if (array_key_exists('fragment', $parts)) $path_info .= '#' . $parts['fragment'];
102 
103  $out = $request["method"]." ".$path_info." ".$request["protocol"]."\r\n"
104  . $request_headers
105  . "Connection: close\r\n\r\n";
106  fwrite($sock, $out);
107  fwrite($sock, $request['payload']);
108 
109  $header_str = stream_get_line($sock, 1024*16, "\r\n\r\n");
110  $headers = http_parse_headers($header_str);
111  $status_line = array_shift($headers);
112 
113  // get http status
114  preg_match('|HTTP/\d+\.\d+\s+(\d+)\s+.*|i',$status_line,$match);
115 
116  $status = '';
117  if (isset($match[1])) {
118  $status=$match[1];
119  }
120 
121  return array('headers' => $headers, 'socket' => $sock, 'status' => $status);
122 }
123 
124 /**
125  * Parses a string containing multiple HTTP header lines into an array
126  * of key => values.
127  * Inspired by HTTP::Daemon (CPAN).
128  */
129 function http_parse_headers($header_str)
130 {
131  $headers = array();
132 
133  //ignore leading blank lines
134  $header_str = preg_replace("/^(?:\x0D?\x0A)+/", '', $header_str);
135 
136  while (preg_match("/^([^\x0A]*?)\x0D?(?:\x0A|\$)/", $header_str, $matches)) {
137  $header_str = substr($header_str, strlen($matches[0]));
138  $status_line = $matches[1];
139 
140  if (empty($headers)) {
141  // the status line
142  $headers[] = $status_line;
143  }
144  elseif (preg_match('/^([^:\s]+)\s*:\s*(.*)/', $status_line, $matches)) {
145  if (isset($key)) {
146  //previous header is finished (was potentially multi-line)
147  $headers[$key] = $val;
148  }
149  list(,$key,$val) = $matches;
150  }
151  elseif (preg_match('/^\s+(.*)/', $status_line, $matches)) {
152  //continue a multi-line header
153  $val .= " ".$matches[1];
154  }
155  else {
156  //empty (possibly malformed) header signals the end of all headers
157  break;
158  }
159  }
160  if (isset($key)) {
161  $headers[$key] = $val;
162  }
163  return $headers;
164 }
165