ArangoDB-PHP API Documentation
  • Namespace
  • Class
  • Deprecated

Namespaces

  • triagens
    • ArangoDb

Classes

  • triagens\ArangoDb\AdminHandler
  • triagens\ArangoDb\AqlUserFunction
  • triagens\ArangoDb\Autoloader
  • triagens\ArangoDb\Batch
  • triagens\ArangoDb\BatchPart
  • triagens\ArangoDb\BindVars
  • triagens\ArangoDb\Collection
  • triagens\ArangoDb\CollectionHandler
  • triagens\ArangoDb\Connection
  • triagens\ArangoDb\ConnectionOptions
  • triagens\ArangoDb\Cursor
  • triagens\ArangoDb\Database
  • triagens\ArangoDb\DefaultValues
  • triagens\ArangoDb\Document
  • triagens\ArangoDb\DocumentHandler
  • triagens\ArangoDb\Edge
  • triagens\ArangoDb\EdgeDefinition
  • triagens\ArangoDb\EdgeHandler
  • triagens\ArangoDb\Endpoint
  • triagens\ArangoDb\Export
  • triagens\ArangoDb\ExportCursor
  • triagens\ArangoDb\Graph
  • triagens\ArangoDb\GraphHandler
  • triagens\ArangoDb\Handler
  • triagens\ArangoDb\HttpHelper
  • triagens\ArangoDb\HttpResponse
  • triagens\ArangoDb\QueryCacheHandler
  • triagens\ArangoDb\QueryHandler
  • triagens\ArangoDb\Statement
  • triagens\ArangoDb\TraceRequest
  • triagens\ArangoDb\TraceResponse
  • triagens\ArangoDb\Transaction
  • triagens\ArangoDb\Traversal
  • triagens\ArangoDb\UpdatePolicy
  • triagens\ArangoDb\UrlHelper
  • triagens\ArangoDb\Urls
  • triagens\ArangoDb\User
  • triagens\ArangoDb\UserHandler
  • triagens\ArangoDb\ValueValidator
  • triagens\ArangoDb\Vertex
  • triagens\ArangoDb\VertexHandler

Exceptions

  • triagens\ArangoDb\ClientException
  • triagens\ArangoDb\ConnectException
  • triagens\ArangoDb\Exception
  • triagens\ArangoDb\ServerException
  1 <?php
  2 
  3 /**
  4  * ArangoDB PHP client: http helper methods
  5  *
  6  * @package   triagens\ArangoDb
  7  * @author    Jan Steemann
  8  * @copyright Copyright 2012, triagens GmbH, Cologne, Germany
  9  */
 10 
 11 namespace triagens\ArangoDb;
 12 
 13 /**
 14  * Helper methods for HTTP request/response handling
 15  *
 16  * @package triagens\ArangoDb
 17  * @since   0.2
 18  */
 19 class HttpHelper
 20 {
 21     /**
 22      * HTTP POST string constant
 23      */
 24     const METHOD_POST = 'POST';
 25 
 26     /**
 27      * HTTP PUT string constant
 28      */
 29     const METHOD_PUT = 'PUT';
 30 
 31     /**
 32      * HTTP DELETE string constant
 33      */
 34     const METHOD_DELETE = 'DELETE';
 35 
 36     /**
 37      * HTTP GET string constant
 38      */
 39     const METHOD_GET = 'GET';
 40 
 41     /**
 42      * HTTP HEAD string constant
 43      */
 44     const METHOD_HEAD = 'HEAD';
 45 
 46     /**
 47      * HTTP PATCH string constant
 48      */
 49     const METHOD_PATCH = 'PATCH';
 50 
 51     /**
 52      * Chunk size (number of bytes processed in one batch)
 53      */
 54     const CHUNK_SIZE = 8192;
 55 
 56     /**
 57      * End of line mark used in HTTP
 58      */
 59     const EOL = "\r\n";
 60 
 61     /**
 62      * Separator between header and body
 63      */
 64     const SEPARATOR = "\r\n\r\n";
 65 
 66     /**
 67      * HTTP protocol version used, hard-coded to version 1.1
 68      */
 69     const PROTOCOL = 'HTTP/1.1';
 70 
 71     /**
 72      * Create a one-time HTTP connection by opening a socket to the server
 73      *
 74      * It is the caller's responsibility to close the socket
 75      *
 76      * @throws ConnectException
 77      *
 78      * @param ConnectionOptions $options - connection options
 79      *
 80      * @return resource - socket with server connection, will throw when no connection can be established
 81      */
 82     public static function createConnection(ConnectionOptions $options)
 83     {
 84         $endpoint = $options[ConnectionOptions::OPTION_ENDPOINT];
 85 
 86         $context = stream_context_create();
 87 
 88         if (Endpoint::getType($endpoint) === Endpoint::TYPE_SSL) {
 89             // set further SSL options for the endpoint
 90             stream_context_set_option($context, 'ssl', 'verify_peer', $options[ConnectionOptions::OPTION_VERIFY_CERT]);
 91             stream_context_set_option($context, 'ssl', 'allow_self_signed', $options[ConnectionOptions::OPTION_ALLOW_SELF_SIGNED]);
 92 
 93             if ($options[ConnectionOptions::OPTION_CIPHERS] !== null) {
 94                 // SSL ciphers
 95                 stream_context_set_option($context, 'ssl', 'ciphers', $options[ConnectionOptions::OPTION_CIPHERS]);
 96             }
 97         }
 98 
 99         $fp = @stream_socket_client(
100             $endpoint,
101             $errNo,
102             $message,
103             $options[ConnectionOptions::OPTION_TIMEOUT],
104             STREAM_CLIENT_CONNECT,
105             $context
106         );
107 
108         if (!$fp) {
109             throw new ConnectException(
110                 'cannot connect to endpoint \'' .
111                 $options[ConnectionOptions::OPTION_ENDPOINT] . '\': ' . $message, $errNo
112             );
113         }
114 
115         stream_set_timeout($fp, $options[ConnectionOptions::OPTION_TIMEOUT]);
116 
117         return $fp;
118     }
119 
120     /**
121      * Boundary string for batch request parts
122      */
123     const MIME_BOUNDARY = 'XXXsubpartXXX';
124 
125     /**
126      * HTTP Header for making an operation asynchronous
127      */
128     const ASYNC_HEADER = 'X-Arango-Async';
129 
130     /**
131      * Create a request string (header and body)
132      *
133      * @param ConnectionOptions $options          - connection options
134      * @param string            $connectionHeader - pre-assembled header string for connection
135      * @param string            $method           - HTTP method
136      * @param string            $url              - HTTP URL
137      * @param string            $body             - optional body to post
138      * @param array             $customHeaders    - any array containing header elements
139      *
140      * @return string - assembled HTTP request string
141      * @throws ClientException
142      *
143      */
144     public static function buildRequest(ConnectionOptions $options, $connectionHeader, $method, $url, $body, array $customHeaders = [])
145     {
146         if (!is_string($body)) {
147             throw new ClientException('Invalid value for body. Expecting string, got ' . gettype($body));
148         }
149 
150         $length = strlen($body);
151 
152         if ($options[ConnectionOptions::OPTION_BATCH] === true) {
153             $contentType = 'Content-Type: multipart/form-data; boundary=' . self::MIME_BOUNDARY . self::EOL;
154         } else {
155             $contentType = '';
156 
157             if ($length > 0 && $options[ConnectionOptions::OPTION_BATCHPART] === false) {
158                 // if body is set, we should set a content-type header
159                 $contentType = 'Content-Type: application/json' . self::EOL;
160             }
161         }
162 
163         $customHeader = '';
164         foreach ($customHeaders as $headerKey => $headerValue) {
165             $customHeader .= $headerKey . ': ' . $headerValue . self::EOL;
166         }
167 
168         // finally assemble the request
169         $request = sprintf('%s %s %s', $method, $url, self::PROTOCOL) .
170             $connectionHeader .   // note: this one starts with an EOL
171             $customHeader .
172             $contentType .
173             sprintf('Content-Length: %s', $length) . self::EOL . self::EOL .
174             $body;
175 
176         return $request;
177     }
178 
179     /**
180      * Validate an HTTP request method name
181      *
182      * @throws ClientException
183      *
184      * @param string $method - method name
185      *
186      * @return bool - always true, will throw if an invalid method name is supplied
187      */
188     public static function validateMethod($method)
189     {
190         if ($method === self::METHOD_POST ||
191             $method === self::METHOD_PUT ||
192             $method === self::METHOD_DELETE ||
193             $method === self::METHOD_GET ||
194             $method === self::METHOD_HEAD ||
195             $method === self::METHOD_PATCH
196         ) {
197             return true;
198         }
199 
200         throw new ClientException('Invalid request method \'' . $method . '\'');
201     }
202 
203     /**
204      * Execute an HTTP request on an opened socket
205      *
206      * It is the caller's responsibility to close the socket
207      *
208      * @param resource $socket  - connection socket (must be open)
209      * @param string   $request - complete HTTP request as a string
210      *
211      * @throws ClientException
212      * @return string - HTTP response string as provided by the server
213      */
214     public static function transfer($socket, $request)
215     {
216         if (!is_resource($socket)) {
217             throw new ClientException('Invalid socket used');
218         }
219 
220         assert(is_string($request));
221 
222         @fwrite($socket, $request);
223         @fflush($socket);
224 
225         $contentLength    = null;
226         $expectedLength   = null;
227         $totalRead        = 0;
228         $contentLengthPos = 0;
229 
230         $result = '';
231         $first  = true;
232 
233         while ($first || !feof($socket)) {
234             $read = @fread($socket, self::CHUNK_SIZE);
235             if ($read === false || $read === '') {
236                 break;
237             }
238 
239             $totalRead += strlen($read);
240 
241             if ($first) {
242                 $result = $read;
243                 $first  = false;
244             } else {
245                 $result .= $read;
246             }
247 
248             if ($contentLength === null) {
249                 // check if content-length header is present
250 
251                 // 12 = minimum offset (i.e. strlen("HTTP/1.1 xxx") -
252                 // after that we could see "content-length:"
253                 $pos = stripos($result, 'content-length: ', 12);
254 
255                 if ($pos !== false) {
256                     $contentLength    = (int) substr($result, $pos + 16, 10); // 16 = strlen("content-length: ")
257                     $contentLengthPos = $pos + 17; // 17 = 16 + 1 one digit
258                 }
259             }
260 
261             if ($contentLength !== null && $expectedLength === null) {
262                 $bodyStart = strpos($result, "\r\n\r\n", $contentLengthPos);
263                 if ($bodyStart !== false) {
264                     $bodyStart += 4; // 4 = strlen("\r\n\r\n")
265                     $expectedLength = $bodyStart + $contentLength;
266                 }
267             }
268 
269             if ($expectedLength !== null && $totalRead >= $expectedLength) {
270                 break;
271             }
272         }
273 
274         return $result;
275     }
276 
277     /**
278      * Splits a http message into its header and body.
279      *
280      * @param string $httpMessage  The http message string.
281      * @param string $originUrl    The original URL the response is coming from
282      * @param string $originMethod The HTTP method that was used when sending data to the origin URL
283      *
284      * @throws ClientException
285      * @return array
286      */
287     public static function parseHttpMessage($httpMessage, $originUrl = null, $originMethod = null)
288     {
289         return explode(self::SEPARATOR, $httpMessage, 2);
290     }
291 
292     /**
293      * Process a string of HTTP headers into an array of header => values.
294      *
295      * @param string $headers - the headers string
296      *
297      * @return array
298      */
299     public static function parseHeaders($headers)
300     {
301         $httpCode  = null;
302         $result    = null;
303         $processed = [];
304 
305         foreach (explode(HttpHelper::EOL, $headers) as $lineNumber => $line) {
306             if ($lineNumber === 0) {
307                 // first line of result is special
308                 if (preg_match("/^HTTP\/\d+\.\d+\s+(\d+)/", $line, $matches)) {
309                     $httpCode = (int) $matches[1];
310                 }
311                 $result = $line;
312             } else {
313                 // other lines contain key:value-like headers
314                 // the following is a performance optimization to get rid of
315                 // the two trims (which are expensive as they are executed over and over) 
316                 if (strpos($line, ': ') !== false) {
317                     list($key, $value) = explode(': ', $line, 2);
318                 } else {
319                     list($key, $value) = explode(':', $line, 2);
320                 }
321                 $processed[strtolower($key)] = $value;
322             }
323         }
324 
325         return [$httpCode, $result, $processed];
326     }
327 }
328 
ArangoDB-PHP API Documentation API documentation generated by ApiGen