Send an HTTP request to a web service
Direct exchanges between web services have become essential. Learn how to send a GET or a POST HTTP(S) request to a web server and how to publish a machine to machine interface.
NOTE: The function sendhttp and its variants of the iZend library is a direct and more complete application of this article.
Scenario
sendfax
is a web service which can fax documents.
A site offers this service via a form.
A user fills in the form and sends it to the site. The site extracts the content of the form and sends it to the service. The service validates the request, runs it and sends back a return code. The site displays the result of the request to the user.
Fax express
Fill in the form and press Fax. Try sending different incomplete forms. NOTE: In this demonstration form, no fax is actually sent.
The navigator generates an HTTP/POST document and sends it to the faxexpress
function of the site.
faxexpress
extracts the content of the document and transmits the list of telephone numbers and the attached file directly to the sendfax
service with the help of the sendhttp
function.
sendfax
analyzes the parameters of the request.
In case of error, if the list of telephone numbers is empty or invalid or if the file is missing or too big, the service sends back an HTTP document containing the text KO_MISSINGNUMBER
, KO_BADNUMBER
, KO_MISSINGFILE
or KO_BADFILE
. NOTE: A real service will probably have other parameters like an identifier and a password.
If all the parameters are correct, the service sends the fax and returns an HTTP document containing the text OK
or KO_FAXNOTSENT
in case of error.
The site analyzes the response returned by the service, the HTTP return code and the content of the document, and returns a document to the navigator containing either a confirmation message or one or several error messages.
The entire dialog between the site and the service, faxpress
and sendfax
, is in proper HTTP.
A sophisticated service can very well return different HTTP error codes, like 400 Bad Request
when a request is rejected or 403 Forbidden
if the requester can't be identified.
The format of the data exchanged between a client and a service can also be more complex and the dialog can be encoded in serialized PHP or in XML.
Making of
Organize the content of your site by creating in the root of the site the folders /public for the pages published on the web, api for service entry points and /library for your own PHP code. Add the files faxexpress.php, sendfax.php and sendhttp.php respectively in the folders public, api and library with the following contents:
- /
- public
- faxexpress.php
- api
- sendfax.php
- library
- sendhttp.php
- public
- <?php
- require_once 'sendhttp.php';
Loads the code of the sendhttp
function.
- define('OK', 'OK');
- define('KO_MISSINGNUMBER', 'KO_MISSINGNUMBER');
- define('KO_BADNUMBER', 'KO_BADNUMBER');
- define('KO_MISSINGFILE', 'KO_MISSINGFILE');
- define('KO_BADFILE', 'KO_BADFILE');
- define('KO_FAXNOTSENT', 'KO_FAXNOTSENT');
Defines the return codes sent back by the service.
- $site_host=$_SERVER['SERVER_NAME'];
- $path='/api/sendfax.php';
- $proto='http';
Initializes the address of the server and the access path to the service. Set $proto
preferably to 'https
' if the server is secured.
- $to=$files=false;
- $action='init';
- if (isset($_POST['fax_send'])) {
- $action='send';
- }
- switch($action) {
- case 'send':
- if (isset($_POST['fax_to'])) {
- preg_match_all('/([\d]+)/', $_POST['fax_to'], $r);
- $to=implode(' ', $r[0]);
- }
- if (isset($_FILES['fax_document']['tmp_name']) and $_FILES['fax_document']['tmp_name']) {
- $name=$_FILES['fax_document']['name'];
- $tmp_name=$_FILES['fax_document']['tmp_name'];
- $type=$_FILES['fax_document']['type'];
- $files=array('document' => array('name' => $name, 'type' => $type, 'tmp_name' => $tmp_name));
- }
- break;
- default:
- break;
- }
Initializes the parameters of the form, extracts the requested action from the form then the list of telephone numbers and the attached file.
- $fax_sent=false;
- $missing_number=false;
- $bad_number=false;
- $missing_file=false;
- $bad_file=false;
- $fax_not_sent=false;
Initializes all the information and error flags of the form. NOTE: The input fields are not validated on purpose to show that a service must in all cases control the data which it is transmitted.
- switch($action) {
- case 'send':
- $url=$proto.'://'.$site_host.$path;
- $args = array(
- 'to' => $to,
- );
- $response=sendpost($url, $args, $files, true);
- if (!$response or $response[0] != 200) {
- $fax_not_sent=true;
- break;
- }
Builds the URL and the parameters of the call to the service. Sends the request with the sendhttp
function.
sendhttp
sends back an array containing the HTTP return code, the header of the response line by line and the body of the response, or false
in case of error.
If the response is false
or if the HTTP return code isn't 200, the communication with the server has failed or the service has rejected the request.
- $r=$response[2];
- $codes=explode(';', $r);
- foreach ($codes as $c) {
- switch($c) {
- case OK:
- $fax_sent=true;
- break;
- case KO_MISSINGNUMBER:
- $missing_number=true;
- break;
- case KO_BADNUMBER:
- $bad_number=true;
- break;
- case KO_MISSINGFILE:
- $missing_file=true;
- break;
- case KO_BADFILE:
- $bad_file=true;
- break;
- case KO_FAXNOTSENT:
- default:
- $fax_not_sent=true;
- break;
- }
- }
- break;
- default:
- break;
- }
- ?>
Extracts the body of the responsee and analyzes the return codes sent back by the service.
The rest of the code displays the input form.
- <h6 id="faxexpress">Fax express</h6>
- <form enctype="multipart/form-data" method="post" action="#faxexpress">
- <p>Who will be receiving the fax?</p>
- <p class="<?php echo ($missing_number or $bad_number) ? 'error' : 'info' ?>">Enter a series of telephone numbers.</p>
- <p><textarea name="fax_to" id="fax_to" cols="50" rows="2"><?php echo $to; ?></textarea></p>
- <p>Which document do you want to fax?</p>
- <p class="<?php echo ($missing_file or $bad_file) ? 'error' : 'info' ?>">Select a text file or an image.</p>
- <p><input type="hidden" name="MAX_FILE_SIZE" value="200000" /><input name="fax_document" id="fax_document" type="file" size="25" /></p>
- <p><input name="fax_send" id="fax_send" type="submit" value="Fax" /></p>
- <?php if ($fax_not_sent): ?>
- <p class="error">Sending the fax has failed.</p>
- <?php elseif ($fax_sent): ?>
- <p class="info">Your fax has been sent.</p>
- <?php else: ?>
- <p class="info">Fill in the form and press Fax.</p>
- <?php endif; ?>
- </form>
- function chkphone($s) {
- return preg_match('/^[0-9]{10,11}$|^\+[0-9]{11}$/', $s);
- }
- function normphone($s) {
- switch(strlen($s)) {
- case 10:
- return '+33'. substr($s,1);
- case 11:
- return '+'. $s;
- default:
- return $s;
- }
- }
checkphone
returns true
if $s
is a telephone number with 10 or 11 digits, false
if not.
normphone
adds the prefix +33 to a telephone number with 10 digits or just a + in front of a telephone number with 11 digits.
NOTE: Adapt these functions for your local dialing.
- define('OK', 'OK');
- define('KO_MISSINGNUMBER', 'KO_MISSINGNUMBER');
- define('KO_BADNUMBER', 'KO_BADNUMBER');
- define('KO_MISSINGFILE', 'KO_MISSINGFILE');
- define('KO_BADFILE', 'KO_BADFILE');
- define('KO_FAXNOTSENT', 'KO_FAXNOTSENT');
- $errorlist=array();
Defines the codes returned by sendhttp
and initializes the list of errors $errorlist
.
- $to=false;
- if (isset($_POST['to'])) {
- $to=$_POST['to'];
- preg_match_all('/(\b[\d+]+\b)/', $to, $tolist);
- $to=$tolist[0];
- }
- if (!$to) {
- $errorlist[]=KO_MISSINGNUMBER;
- }
- else {
- $tellist=array();
- foreach ($to as $tel){
- if (!chkphone($tel)) {
- $errorlist[]=KO_BADNUMBER;
- break;
- }
- $tellist[] = normphone($tel);
- }
- $to=array_unique($tellist);
- }
Extracts groups of consecutive digits from $_POST['to']
.
Checks that all the groups of digits are telephone numbers, normalizes them and deletes duplicated numbers.
Adds the error KO_MISSINGNUMBER
to $errorlist
if the list of telephone numbers is empty.
Adds the error KO_BADNUMBER
to $errorlist
if a number is invalid.
- $data=false;
- $maxsize = 200000;
- if (!isset($_FILES['document']) or !$_FILES['document']['tmp_name'] or $_FILES['document']['error'] != 0) {
- $errorlist[]=KO_MISSINGFILE;
- }
- else if ($_FILES['document']['size'] == 0 or $_FILES['document']['size'] > $maxsize) {
- $errorlist[]=KO_BADFILE;
- }
- if (!$errorlist) {
- $tmpdir = '/tmp';
- $filecopy = $tmpdir . '/' . basename( $_FILES['document']['name']);
- if (move_uploaded_file($_FILES['document']['tmp_name'], $filecopy)) {
- $data64=@file_get_contents($filecopy);
- $data=base64_decode($data64);
- if ($data === false) {
- $errorlist[]=KO_BADFILE;
- }
- /* fax document */
- @unlink($filecopy);
- }
- else {
- $errorlist[]=KO_BADFILE;
- }
- }
Extracts the attached file from $_FILES
.
Adds the error KO_MISSINGFILE
to $errorlist
if the file is missing or in case of a transfer error.
Adds the error KO_BADFILE
to $errorlist
if the size of the file is invalid.
If no error has been found, copies the transmitted file in a temporary zone and decodes its content.
Sends the fax. Adds KO_FAXNOTSENT
to $errorlist
in case of error.
Deletes the temporaty file.
NOTE: In this demonstration version, the fax isn't really sent.
- if (!$errorlist) {
- $errorlist[]=OK;
- }
Initializes the list of return codes to OK
if no error has been encountered.
- header('Content-Type: text/plain');
- echo implode(';', $errorlist); // just one line with no newline
Sends back a document of the type text/plain
consisting of a single line of text containing one or several return codes separated by a ; (SEMICOLON).
- function sendhttp($method, $url, $args, $files=false, $base64=false) {
sendhttp
accepts 4 arguments: a request type, a URL, a liste of parameters and an optional list of files.
- $purl = parse_url($url);
- if ($purl === false)
- {
- return false;
- }
Decomposes the URL with the PHP function parse_url
. Returns false
if $url
is invalid.
- $scheme = isset($purl['scheme']) ? $purl['scheme'] : 'http';
- switch($scheme) {
- case 'https':
- $proto = 'ssl';
- break;
- case 'http':
- $proto = 'tcp';
- break;
- default:
- return false;
- }
- $host = isset($purl['host']) ? $purl['host'] : 'localhost';
- $portnum = isset($purl['portnum']) ? $purl['portnum'] : $scheme == 'https' ? 443 : 80;
- $path = isset($purl['path']) ? $purl['path'] : '';
Extracts the different components of the URL and assigns default values.
- $user_agent = 'iZend';
- $header_string = $content_string = '';
- switch ($method) {
Initializes the header and the body of the HTTP document. Tests the type of the request.
- case 'POST':
- if ($files && is_array($files)) {
- $boundary = md5(microtime());
- $content_type = 'multipart/form-data; boundary='.$boundary;
- $content_string = '';
- if ($args && is_array($args)) {
- foreach ($args as $k => $v) {
- $content_string .= '--' . $boundary . "\r\n";
- $content_string .= 'Content-Disposition: form-data; name="' . $k . '"' . "\r\n\r\n" . $v . "\r\n";
- }
- }
- foreach ($files as $k => $v ) {
- $data = file_get_contents($v['tmp_name']);
- if ($data === false) {
- break;
- }
- $content_string .= '--' . $boundary . "\r\n";
- $content_string .= 'Content-Disposition: form-data; name="' . $k . '"; filename="' . $v['name'] . '"' . "\r\n";
- $content_string .= 'Content-Type: ' . $v['type'] . "\r\n";
- if ($base64) {
- $content_string .= 'Content-Transfer-Encoding: base64' . "\r\n\r\n";
- $content_string .= chunk_split(base64_encode($data)) . "\r\n";
- }
- else {
- $content_string .= 'Content-Transfer-Encoding: binary' . "\r\n\r\n";
- $content_string .= $data . "\r\n";
- }
- }
- $content_string .= '--' . $boundary . '--'. "\r\n";
- }
- else {
- $content_type = 'application/x-www-form-urlencoded';
- if ($args && is_array($args)) {
- $content_string = http_build_args($args);
- }
- }
- $content_length = strlen($content_string);
- $header_string="POST $path HTTP/1.0\r\nHost: $host\r\nUser-Agent: $user_agent\r\nContent-Type: $content_type\r\nContent-Length: $content_length\r\nConnection: close\r\n\r\n";
- break;
If the request is a POST, if the document has one or several attached files, writes a document of the type multipart/form-data
, otherwise writes a document of the type application/x-www-form-urlencoded
.
In the first case, builds the body of the document with a first part which defines a unique separator then a series of secondary parts each containing one of the attached files encoded in base64.
In the second case, builds the body of an HTTP document containing the parameters of the request, if any, properly formatted with http_build_args
.
- case 'GET':
- if ($args && is_array($args)) {
- $path .= '?'.http_build_args($args);
- }
- $header_string="GET $path HTTP/1.0\r\nHost: $host\r\nUser-Agent: $user_agent\r\nConnection: close\r\n\r\n";
- break;
If the request is a GET, adds the parameters, if any, properly formatted with http_build_args
after the access path, then builds the header of the HTTP document. The body of the document is empty.
- default:
- return false;
- }
Returns false
if $method
isn't 'POST'
or 'GET'
.
- $socket = @fsockopen($proto.'://'.$host, $portnum);
- if ($socket === false) {
- return false;
- }
Opens the connection with the server. Returns false
in case of error.
- if (fwrite($socket, $header_string) === false) {
- return false;
- }
- if ($content_string) {
- $content_len = strlen($content_string);
- for ($written = 0; $written < $content_len; $written += $w) {
- $w = fwrite($socket, $written == 0 ? $content_string : substr($content_string, $written));
- if ($w === false) {
- return false;
- }
- }
- }
Writes the header of the document then the body of the document in several packets if necessary.
- $response = '';
- while (!feof($socket)) {
- $response .= fread($socket, 8192);
- }
Reads the response from the server.
- fclose($socket);
Closes the connection with the server.
- list($response_headers, $response_body) = explode("\r\n\r\n", $response, 2);
- $response_header_lines = explode("\r\n", $response_headers);
- $http_response_line = array_shift($response_header_lines);
- if (preg_match('@^HTTP/[0-9]\.[0-9] ([0-9]{3})@', $http_response_line, $r)) {
- $response_code = $r[1];
- }
- else {
- $response_code = 0;
- }
Splits the header and the body of the document. Separates the header line by line. Extracts the first line of the header to get the return code.
- $response_header_array = array();
- foreach ($response_header_lines as $header_line) {
- list($header, $value) = explode(': ', $header_line, 2);
- $response_header_array[$header] = $value;
- }
Saves the header of the response in an associative array.
- return array($response_code, $response_header_array, $response_body);
- }
Returns an array with the HTTP code sent back by the service, the MIME header of the document in an associative array and the plain content of the response.
- function sendget($url, $args) {
- return sendhttp('GET', $url, $args);
- }
sendget
returns the result of calling sendhttp
with the arguments $url
and $args
and $method
set to 'GET'
.
- function sendpost($url, $args, $files=false, $base64=false ) {
- return sendhttp('POST', $url, $args, $files, $base64);
- }
sendpost
returns the result of calling sendhttp
with the arguments $url
, $args
and $files
and $base64
and $method
set to 'POST'
.
- function http_build_args($args) {
- $args_string = '';
- foreach ($args as $name => $value) {
- $args_string .= ($args_string ? '&' : '') . urlencode($name) . '=' . urlencode($value);
- }
- return $args_string;
- }
http_build_args
returns the associative array $args
in the form of a properly formatted URL string of parameters.
sendhttp
SYNOPSIS
sendhttp( $method, $url, $args, $files=false, $base64=false )
sendget( $url, $args )
sendpost( $url, $args, $files=false, $base64=false )
DESCRIPTION
sendhtpp
produces a GET or a POST HTTP 1.0 document, transmits it to an HTTP server and returns the HTTP code, the header and the body of the document sent back by the server or false
in case of error.
$method
is set to 'GET'
or 'POST'
depending on the type of request expected by the server.
$url
addresses the PHP code of the server which is going to analyze the document, run an action and return a regular HTTP document.
$url
has the format [proto://]host[:portnum]/path.
Set proto to 'https'
to transmit a document in a secure mode.
host gives the name or the IP address of the server. If necessary, add portnum to specify a port number different from 80 or 443 for HTTP or HTTPS connections with a server listening to particular ports.
path gives the path to the PHP code on the server.
$args
contains a list of parameters for the called service arranged in an associative array { 'param1' => val1, ... }
.
$args
can pass one or several fields directly in a URL for an HTTP GET request or extracted from an HTML form for an HTTP POST request.
$files
contains the list of files attached to the request in the form an associative array { 'docname' => {'name' => 'filename', 'type' => 'mimetype', 'tmp_name' => 'pathname'}, ... }
. This parameter is optional. Typically, it's used to pass a file transmitted by an <input type="file" name="docname"... />
tag in an HTML form.
If $base64
is true
, the contents of the files are encoded in base64.
sendhttp
sends back an array containing the HTTP return code, the header of the response and the body of the response. If an argument is invalid or if the connection has failed, sendhttp
returns false
.
sendget
and sendpost
call sendhttp
with $method
respectively set to 'GET'
and 'POST'
.
EXAMPLE
$ php -a
Runs the PHP interpreter in interactive mode.
php> $r = sendget('http://www.google.com/search', array('q' => 'frasq.org'));
Same as typing http:://www.google.com/search?q=frasq.org in the address bar of a navigator.
php> print_r($r[0]);
200
Displays the HTTP return code.
php> print_r($r[1]);
Array
(
[Date] => ...
[Expires] => -1
[Cache-Control] => private, max-age=0
[Content-Type] => text/html; charset=ISO-8859-1
...
)
Displays the MIME header of the document.
php> echo $r[1]['Content-Type'];
text/html; charset=ISO-8859-1
Displays the content type of the document.
php> print_r($r[2]);
<!doctype html><head><title>frasq.org - Google Search</title> ...
Displays the document.
Application
Google publishes an API for drawing charts and other images - Google Charts API.
One function can be used to obtain a PNG image of a QR code. Simply pass the proper parameters in an <img>
:
<img src="http://chart.googleapis.com/chart?cht=qr&chf=bg,s,ffffff&chs=63x63&chld=M|0&chl=www.frasq.org" />
To call this service in a program, wrap sendhttp
in a specific function:
- require_once 'sendhttp.php';
Loads the sendhttp
function.
- function qrencode($s, $size=100, $quality='M') {
- $url = 'http://chart.googleapis.com/chart';
- $args = array(
- 'cht' => 'qr',
- 'chf' => 'bg,s,ffffff',
- 'chs' => "${size}x${size}",
- 'chld' => "${quality}|0",
- 'chl' => $s,
- );
- $response=sendget($url, $args);
- if (!$response or $response[0] != 200) {
- return false;
- }
- return $response[2];
- }
qrencode
sends a GET request to the service at the address http://chart.googleapis.com/chart and returns the binary image data or false
in case of error.
$s
contains the string of characters to be encoded.
$size
is in pixels.
$quality
can be either L
, M
, Q
or H
.
See the documentation for all the details.
Decoding the image of a QR code is more challenging. Suppose you haven't found a true HTTP API but just a form which lets you upload an image and returns the decoded text: ZXing Decoder Online. Try it. This form returns the decoded text or another HTML document in case of error.
Extract the source code of the form:
<form enctype="multipart/form-data" method="post" action="decode">
<p><input name="f" size="50" type="file"/> <input type="submit"/>
<input value="true" name="full" type="hidden"/></p></form>
Write a function which passes these parameters in an HTTP POST request:
- function qrdecode($file, $filetype='image/png') {
- $url = 'http://zxing.org/w/decode';
- $args = array(
- 'full' => 'true',
- );
- $files=array('f' => array('name' => basename($file), 'tmp_name' => $file, 'type' => $filetype));
- $response=sendpost($url, $args, $files, false); // DON'T encode data in base64
- if (!$response or $response[0] != 200) {
- return false;
- }
- if (preg_match('#<html>.*</html>#', $response[2])) {
- return false;
- }
- return strip_tags($response[2]);
- }
qrdecode
sends a POST request at the address http://zxing.org/w/decode with an attached file.
If the service returns another HTML document, qrdecode
returns false
.
IMPORTANT: Be very careful when inserting data returned by another program. If possible, remove all tags with strip_tags
and add your own formatting.
Read the manual Write a CMS in PHP to learn how to structure the development of a web site.
Comments