*/ function method_to_oas( string $http_method ) : string { return \strtolower($http_method); } /** * @param string $input * @return \alveolata\http\struct_request * @author Christian Fraß */ function request_decode( string $input ) : \alveolata\http\struct_request { $input_ = str_replace("\r\n", "\n", $input); $parts = explode("\n", $input_); $chunks = []; $request_method = null; $request_path = null; $request_protocol = null; $request_headers = []; $request_body = ''; foreach ($parts as $part) { if (preg_match('/^([A-Z]+) ([\S]*) (.*)$/', $part, $matches) === 1) { $request_method = strtoupper($matches[1]); $request_path = $matches[2]; $request_protocol = $matches[3]; } else if (preg_match('/^([^:]+): (.*)$/', $part, $matches) === 1) { $request_headers[$matches[1]] = $matches[2]; } else if ($part !== '') { array_push($chunks, $part); } else { // ignore } } $request_body .= implode("\n", $chunks); $request = new \alveolata\http\struct_request( $request_protocol, $request_method, $request_path, $request_headers, $request_body ); return $request; } /** * @param \alveolata\http\struct_response $response * @return string * @author Christian Fraß */ function response_encode( \alveolata\http\struct_response $response ) : string { $code_text_map = [ '100' => 'Continue', '101' => 'Switching Protocols', '103' => 'Early Hints', '200' => 'OK', '201' => 'Created', '202' => 'Accepted', '203' => 'Non-Authoritative Information', '204' => 'No Content', '205' => 'Reset Content', '206' => 'Partial Content', '300' => 'Multiple Choices', '301' => 'Moved Permanently', '302' => 'Found', '303' => 'See Other', '304' => 'Not Modified', '307' => 'Temporary Redirect', '308' => 'Permanent Redirect', '400' => 'Bad Request', '401' => 'Unauthorized', '402' => 'Payment Required', '403' => 'Forbidden', '404' => 'Not Found', '405' => 'Method Not Allowed', '406' => 'Not Acceptable', '407' => 'Proxy Authentication Required', '408' => 'Request Timeout', '409' => 'Conflict', '410' => 'Gone', '411' => 'Length Required', '412' => 'Precondition Failed', '413' => 'Payload Too Large', '414' => 'URI Too Long', '415' => 'Unsupported Media Type', '416' => 'Range Not Satisfiable', '417' => 'Expectation Failed', '418' => 'I\'m a teapot', '422' => 'Unprocessable Entity', '424' => 'Failed Dependency', '425' => 'Too Early', '426' => 'Upgrade Required', '428' => 'Precondition Required', '429' => 'Too Many Requests', '431' => 'Request Header Fields Too Large', '451' => 'Unavailable For Legal Reasons', '500' => 'Internal Server Error', '501' => 'Not Implemented', '502' => 'Bad Gateway', '503' => 'Service Unavailable', '504' => 'Gateway Timeout', '505' => 'HTTP Version Not Supported', '506' => 'Variant Also Negotiates', '507' => 'Insufficient Storage', '508' => 'Loop Detected', '510' => 'Not Extended', '511' => 'Network Authentication Required', ]; $output = implode( "\r\n", array_merge( [ sprintf('HTTP/1.1 %d %s', $response->statuscode, $code_text_map[strval($response->statuscode)]), ], array_map( function ($key) use (&$response) { $value = $response->headers[$key]; return sprintf('%s: %s', $key, $value); }, array_keys($response->headers) ), [ '', $response->body, ] ) ); return $output; } /** * sends an HTTP request and returns the response * requires CURL plugin * * @param array $options { * record< * timeout ?: float, * > * } */ function call( \alveolata\http\struct_request $request, array $options = [] ): \alveolata\http\struct_response { $options = \array_merge( [ 'timeout' => 5.0 ], $options ); \alveolata\log\debug( 'http_call_request', [ 'request' => [ 'protocol' => $request->protocol, 'method' => $request->method, 'target' => $request->target, 'headers' => $request->headers, 'body' => $request->body, ], ] ); $request_headers_transformed = []; foreach ($request->headers as $key => $value) { \array_push($request_headers_transformed, \sprintf('%s: %s', $key, $value)); } $curl_object = \curl_init(); // set common options \curl_setopt($curl_object, \CURLOPT_CONNECTTIMEOUT, $options['timeout']); \curl_setopt($curl_object, \CURLOPT_TIMEOUT, $options['timeout']); \curl_setopt($curl_object, \CURLOPT_FOLLOWLOCATION, true); // set request options \curl_setopt($curl_object, \CURLOPT_URL, $request->target); \curl_setopt($curl_object, \CURLOPT_CUSTOMREQUEST, $request->method); \curl_setopt($curl_object, \CURLINFO_HEADER_OUT, true); \curl_setopt($curl_object, \CURLOPT_HTTPHEADER, $request_headers_transformed); \curl_setopt($curl_object, \CURLOPT_SSL_VERIFYPEER, /*$options['sslVerifyPeer']*/1); \curl_setopt($curl_object, \CURLOPT_SSL_VERIFYHOST, /*$options['sslVerifyHost']*/2); if ( \in_array( $request->method, [ \alveolata\http\enum_method::post, \alveolata\http\enum_method::put, \alveolata\http\enum_method::patch, ] ) ) { \curl_setopt($curl_object, \CURLOPT_POSTFIELDS, $request->body); } // set response options \curl_setopt($curl_object, \CURLOPT_RETURNTRANSFER, true); \curl_setopt($curl_object, \CURLOPT_HEADER, 1); $response_raw = \curl_exec($curl_object); if ($response_raw === false) { $error_number = \curl_errno($curl_object); $error_message = \curl_error($curl_object); \curl_close($curl_object); throw (new \Exception( \sprintf( '%s | %s', 'http_call_failed', \sprintf( \json_encode( [ 'error_number' => $error_number, 'error_message' => $error_message, 'method' => $request->method, 'target' => $request->target, ] ) ) ) )); } else { $header_size = \curl_getinfo($curl_object, \CURLINFO_HEADER_SIZE); // statuscode { $statuscode = \intval(\curl_getinfo($curl_object, \CURLINFO_HTTP_CODE)); } // headers { $headers_raw = \substr($response_raw, 0, $header_size); $header_parts = \explode("\r\n", $headers_raw); // throw away first part, containing the status code information \array_shift($header_parts); $headers = []; foreach ($header_parts as $part) { if ($part === '') { // do nothing } else if (\preg_match('/^HTTP\/.* [0-9]{3} .*$/', $part) === 1) { // e.g. "HTTP\/1.1 200 OK" // do nothing } else { $funds = null; $matching = (\preg_match('/([^:]*): (.*)/', $part, $funds) === 1); if (! $matching) { \alveolata\log\warning( 'http_call_malformed_header', [ 'header' => $part, ] ); } else { $key = $funds[1]; $value = $funds[2]; $headers[$key] = $value; } } } } // body { $body = \substr($response_raw, $header_size); } \curl_close($curl_object); $response = new \alveolata\http\struct_response($statuscode, $headers, $body); \alveolata\log\debug( 'http_call_response', [ 'response' => [ 'statuscode' => $response->statuscode, 'headers' => $response->headers, 'body' => $response->body, ], ] ); return $response; } } ?>