diff --git a/module/VuFindHttp/.gitignore b/module/VuFindHttp/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6b567c718f3d9032cbd8f8c0729e257faffa3913 --- /dev/null +++ b/module/VuFindHttp/.gitignore @@ -0,0 +1,7 @@ +TAGS +\#* +.\#* +ChangeLog +review +test.php +vendor/ diff --git a/module/VuFindHttp/LICENSE b/module/VuFindHttp/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4eb3f1e9596a3213c1eeba7ec401ecac70ff1801 --- /dev/null +++ b/module/VuFindHttp/LICENSE @@ -0,0 +1,278 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. diff --git a/module/VuFindHttp/Module.php b/module/VuFindHttp/Module.php new file mode 100644 index 0000000000000000000000000000000000000000..472f3c95c77d2db8f8075e57039800d34e98fd78 --- /dev/null +++ b/module/VuFindHttp/Module.php @@ -0,0 +1,115 @@ +<?php + +/** + * ZF2 module definition for the VF2 proxy service + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Proxy + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindHttp; + +/** + * ZF2 module definition for the VF2 HTTP service. + * + * @category Proxy + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +class Module +{ + + /** + * Relative path to search service configuration. + * + * @var string + */ + protected $configPath = 'config/http.conf.php'; + + /** + * Return autoloader configuration. + * + * @return array + */ + public function getAutoloaderConfig () + { + return array( + 'Zend\Loader\StandardAutoloader' => array( + 'namespaces' => array( + __NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__, + ), + ), + ); + } + + /** + * Return module configuration. + * + * @return array + */ + public function getConfig () + { + return array(); + } + + /** + * Initialize module. + * + * @return void + */ + public function init () + { + } + + /** + * Return service configuration. + * + * @return array + */ + public function getServiceConfig () + { + return array( + 'factories' => array( + 'VuFind\Http' => array($this, 'setup'), + ) + ); + } + + /** + * Return configured http service to superior service manager. + * + * @return \VuFind\Service\Search + */ + public function setup () + { + $config = include realpath(__DIR__ . '/' . $this->configPath); + + $di = new \Zend\Di\Di(); + $di->configure(new \Zend\Di\Config($config)); + return $di->get('VuFindHttp\HttpService'); + } + +} \ No newline at end of file diff --git a/module/VuFindHttp/README.org b/module/VuFindHttp/README.org new file mode 100644 index 0000000000000000000000000000000000000000..d824c0051ddcaa1eb2bc8f85578d98784656f4f9 --- /dev/null +++ b/module/VuFindHttp/README.org @@ -0,0 +1,114 @@ +* Proxy service API + +The terms MUST NOT, SHOULD NOT, SHOULD, MUST, and MAY are used as defined by [RFC2119]. + +The proxy service provides access to HTTP-based internal and external resources. The services can be +configured to proxy requests to external resources identified by the host part of the http URI. + +The following hosts are considered as as internal resources: + + - localhost (IPv4 local host) + - 127/8 (IPv4 loopback network addresses) + - [::1] (IPv6 loopback address) + +Other VF2 components SHOULD use the proxy service in order to integrate in environments that require +proxy access. + +* Public API + +The proxy service implements the following public methods: + +** get(string URL[, array PARAMS[, float TIMEOUT]]) + +Performs a GET request to URL using optional PARAMS and TIMEOUT, and returns the response. + +PARAMS is an array of request parameters. It can either be an associative array of parameter names +and associated values or a numeric array with properly URL-encoded name-value pairs in the form +`NAME=VALUE' + +In the first case the query string is created with PHP's built-in http_build_query() function +([BUILDQUERY]), in the latter case the name-value pairs are simply concatenated to form the query +string. + +TIMEOUT is a float with the request timeout in seconds. + +** postForm(string URL[, array PARAMS[, float TIMEOUT]]) + +Performs a POST request of regular form data to URL using optional PARAMS and optional +HEADERS. PARAMS is converted to a query string using the same rules as in get() and send inside a +POST request body with a content type of `application/x-www-form-urlencoded'. + +TIMEOUT is a float with the request timeout in seconds. + +** post(string URL[, mixed BODY[, string TYPE[, float TIMEOUT]]]) + +Performs a POST request of optional BODY of TYPE to URL and returns the repose. + +BODY is casted to string. + +A component using the service SHOULD provide an appropriate Content-Type header for the POST request +body in accordance with [MIMETYPES]. The default MIME type is `application/octet-stream'. The proxy +service calculates the Content-Length request header value by performing `strlen()' on the request +body. + +TIMEOUT is a float with the request timeout in seconds. + +** proxify(Zend\Http\Client CLIENT[, array OPTIONS]) + +Add proxy settings to CLIENT with optional OPTIONS. + +CLIENT is an existing Zend HTTP client. The proxify() method replaces the HTTP connection adapter by +a Zend Framework HTTP proxy adapter. OPTIONS is an array of Zend HTTP proxy options that should be +used in addition to the service's proxy configuration. The proxy service proxy configuration is +superior to the ones supplied via OPTIONS. + +** createClient(string URL[, string METHOD, [float TIMEOUT]]) + +Return new Zend HTTP client to access URI by optional METHOD with optional TIMEOUT. CLIENT is +proxified using the service's proxify() function. + +* Error handling + +The proxy service uses a marker interface [MARKER INTERFACE] to mark its exception. The proxy +service catches all exceptions from the Zend HTTP client and wraps them in either a proxy service +specific RuntimeException or InvalidArgumentException. The proxy service does not catch or emit any +other exceptions. + +* Configuration + +The proxy service is configured by an PHP array in `conf/proxy.conf.php'. This array MUST contain +the key `proxy' which points to an associative array with proxy configuration and MAY contain a key +`adapter' with the name or an instance of a Zend HTTP client adapter class. + +The `proxy' configuration array has the same semantics as the Zend HTTP proxy adapter +configuration[ZEND ADAPTER]. No proxying is done if the proxy configuration array is empty. + +The class denoted in the `adapter' key is used as the HTTP connection adapter for get(), post(), and +postForm(). + +* Extension points + +The proxy service does not provide any extensions points. + +* Service identifier + +The proxy service registers under the name `VuFind\Http' in the web application's top level +service manager. + +* Implementation + +A first implementation of this specification can be found at [VF2 PROXY]. + +* References + +[BUILDQUERY] : http://php.net/manual/de/function.http-build-query.php + +[MARKER INTERFACE] : Marker interface pattern, http://en.wikipedia.org/wiki/Marker_interface_pattern + +[MIMETYPES] : MIME Media Types, http://www.iana.org/assignments/media-types/index.html + +[RFC2119] : Key words for use in RFCs to Indicate Requirement Levels, http://www.ietf.org/rfc/rfc2119.txt + +[VF2 PROXY] : https://github.com/dmj/vf2-proxy + +[ZEND ADAPTER] : Zend_Http_Client - Connection Adapters, http://packages.zendframework.com/docs/latest/manual/en/modules/zend.http.client.adapters.html diff --git a/module/VuFindHttp/config/http.conf.php b/module/VuFindHttp/config/http.conf.php new file mode 100644 index 0000000000000000000000000000000000000000..4e539c30e99d6e77873fbfcacdb11e3b2ae5856c --- /dev/null +++ b/module/VuFindHttp/config/http.conf.php @@ -0,0 +1,11 @@ +<?php + +return array( + 'instance' => array( + 'VuFindHttp\HttpService' => array( + 'parameters' => array( + 'proxyConfig' => array(), + ) + ) + ) +); diff --git a/module/VuFindHttp/src/VuFindHttp/Exception/ExceptionInterface.php b/module/VuFindHttp/src/VuFindHttp/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..6d87354613d7e07624dfbe9e465f1ebedd6fb28f --- /dev/null +++ b/module/VuFindHttp/src/VuFindHttp/Exception/ExceptionInterface.php @@ -0,0 +1,43 @@ +<?php + +/** + * Proxy system exception marker interface. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Http + * @package Exception + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindHttp\Exception; + +/** + * Proxy system exception marker interface. + * + * @category Http + * @package Exception + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ +interface ExceptionInterface +{ +} \ No newline at end of file diff --git a/module/VuFindHttp/src/VuFindHttp/Exception/RuntimeException.php b/module/VuFindHttp/src/VuFindHttp/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..0d51397908bf2702335e9669c76bb3585802251a --- /dev/null +++ b/module/VuFindHttp/src/VuFindHttp/Exception/RuntimeException.php @@ -0,0 +1,46 @@ +<?php + +/** + * Proxy system RuntimeException. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Http + * @package Exception + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindHttp\Exception; + +/** + * Proxy system RuntimeException. + * + * This exception is thrown if the request failed on the network level + * (e.g. timeout). + * + * @category Http + * @package Exception + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} \ No newline at end of file diff --git a/module/VuFindHttp/src/VuFindHttp/HttpService.php b/module/VuFindHttp/src/VuFindHttp/HttpService.php new file mode 100644 index 0000000000000000000000000000000000000000..728c5dbbca729c26659cc7c571c3531781c6a142 --- /dev/null +++ b/module/VuFindHttp/src/VuFindHttp/HttpService.php @@ -0,0 +1,244 @@ +<?php + +/** + * VuFind HTTP service class file. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-HTTP + */ + +namespace VuFindHttp; + +/** + * VuFind HTTP service. + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-search-subsystem + */ +class HttpService implements HttpServiceInterface +{ + + /** + * Regular expression matching a request to localhost. + * + * @var string + */ + const LOCAL_ADDRESS_RE = '@^(localhost|127(\.\d+){3}|\[::1\])@'; + + /** + * Proxy configuration. + * + * @see \Zend\Http\Client\Adapter\Proxy::$config + * + * @var array + */ + protected $proxyConfig; + + /** + * Constructor. + * + * @param array $proxyConfig Proxy configuration + * + * @return void + */ + public function __construct (array $proxyConfig = array()) + { + $this->proxyConfig = $proxyConfig; + } + + /** + * Proxify an existing client. + * + * Returns the client given as argument with appropriate proxy setup. + * + * @param Zend\Http\Client $client HTTP client + * @param array $options ZF2 ProxyAdapter options + * + * @return Zend\Http\Client + */ + public function proxify (\Zend\Http\Client $client, array $options = array()) + { + if ($this->proxyConfig) { + $host = $client->getUri()->getHost(); + if (!$this->isLocal($host)) { + $adapter = new \Zend\Http\Client\Adapter\Proxy(); + $options = array_replace($this->proxyConfig, $options); + $adapter->setOptions($options); + $client->setAdapter($adapter); + } + } + return $client; + } + + /** + * Perform a GET request. + * + * @param string $url Request URL + * @param array $params Request parameters + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function get ($url, array $params = array(), $timeout = null) + { + if ($params) { + $query = $this->createQueryString($params); + if (strpos($url, '?') !== false) { + $url .= '&' . $query; + } else { + $url .= '?' . $query; + } + } + $client = $this->createClient($url, \Zend\Http\Request::METHOD_GET, $timeout); + return $this->send($client); + } + + /** + * Perform a POST request. + * + * @param string $url Request URL + * @param mixed $body Request body document + * @param string $type Request body content type + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function post ($url, $body = null, $type = 'application/octet-stream', $timeout = null) + { + $client = $this->createClient($url, \Zend\Http\Request::METHOD_POST, $timeout); + $client->setRawBody($body); + $client->setHeaders(array('Content-Type' => $type, 'Content-Length' => strlen($body))); + return $this->send($client); + } + + /** + * Post form data. + * + * @param string $url Request URL + * @param array $params Form data + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function postForm ($url, array $params = array(), $timeout = null) + { + $body = $this->createQueryString($params); + return $this->post($url, $body, \Zend\Http\Client::ENC_URLENCODED, $timeout); + } + + /** + * Return a new HTTP client. + * + * @param string $url Target URL + * @param string $method Request method + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Client + */ + public function createClient ($url, $method = \Zend\Http\Request::METHOD_GET, $timeout = null) + { + $client = new \Zend\Http\Client(); + $client->setMethod($method); + $client->setUri($url); + if ($timeout) { + $client->setOptions(array('timeout' => $timeout)); + } + $this->proxify($client); + return $client; + } + + /// Internal API + + /** + * Return query string based on params. + * + * @param array $params Parameters + * + * @return string + */ + protected function createQueryString (array $params = array()) + { + if ($this->isAssocParams($params)) { + return http_build_query($params); + } else { + return implode('&', $params); + } + } + + /** + * Send HTTP request and return response. + * + * @param \Zend\Http\Client $client HTTP client to use + * + * @throws Exception\RuntimeException + * @return \Zend\Http\Response + * + * @todo Catch more exceptions, maybe? + */ + protected function send (\Zend\Http\Client $client) + { + try { + $response = $client->send(); + } catch (\Zend\Http\Client\Exception\RuntimeException $e) { + throw new Exception\RuntimeException( + sprintf('Zend HTTP Client exception: %s', $e), + -1, + $e + ); + } + return $response; + } + + /** + * Return TRUE if argument is an associative array. + * + * @param array $array Array to test + * + * @return boolean + */ + public static function isAssocParams (array $array) + { + foreach ($array as $key => $value) { + if (!is_numeric($key)) { + return true; + } + } + return false; + } + + /** + * Return TRUE if argument refers to localhost. + * + * @param string $host Host to check + * + * @return boolean + */ + protected function isLocal ($host) + { + return preg_match(self::LOCAL_ADDRESS_RE, $host); + } + +} \ No newline at end of file diff --git a/module/VuFindHttp/src/VuFindHttp/HttpServiceAwareInterface.php b/module/VuFindHttp/src/VuFindHttp/HttpServiceAwareInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..16153250787d9f2caa42e4c886644e2bf8a89747 --- /dev/null +++ b/module/VuFindHttp/src/VuFindHttp/HttpServiceAwareInterface.php @@ -0,0 +1,52 @@ +<?php + +/** + * Marker interface for HTTP service aware classes. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindHttp; + +/** + * Marker interface for HTTP service aware classes. + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +interface HttpServiceAwareInterface +{ + /** + * Set the HTTP service to be used for HTTP requests. + * + * @param HttpServiceInterface $service HTTP service + * + * @return void + */ + public function setHttpService (HttpServiceInterface $service); +} \ No newline at end of file diff --git a/module/VuFindHttp/src/VuFindHttp/HttpServiceInterface.php b/module/VuFindHttp/src/VuFindHttp/HttpServiceInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..456f59f938b39bb9267d9d5fd2a481f100783ae0 --- /dev/null +++ b/module/VuFindHttp/src/VuFindHttp/HttpServiceInterface.php @@ -0,0 +1,101 @@ +<?php + +/** + * VuFind HTTP service interface definition. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindHttp; + +/** + * VuFind HTTP service interface definition. + * + * @category Http + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +interface HttpServiceInterface +{ + /** + * Proxify an existing client. + * + * Returns the client given as argument with appropriate proxy setup. + * + * @param Zend\Http\Client $client HTTP client + * @param array $options ZF2 ProxyAdapter options + * + * @return Zend\Http\Client + */ + public function proxify (\Zend\Http\Client $client, array $options = array()); + + /** + * Perform a GET request. + * + * @param string $url Request URL + * @param array $params Request parameters + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function get ($url, array $params = array(), $timeout = null); + + /** + * Perform a POST request. + * + * @param string $url Request URL + * @param mixed $body Request body document + * @param string $type Request body content type + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function post ($url, $body = null, $type = 'application/octet-stream', $timeout = null); + + /** + * Post form data. + * + * @param string $url Request URL + * @param array $params Form data + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Response + */ + public function postForm ($url, array $params = array(), $timeout = null); + + /** + * Return a new proxy client. + * + * @param string $url Target URL + * @param string $method Request method + * @param float $timeout Request timeout in seconds + * + * @return \Zend\Http\Client + */ + public function createClient ($url, $method = \Zend\Http\Request::METHOD_GET, $timeout = null); + +} \ No newline at end of file diff --git a/module/VuFindHttp/tests/unit-tests/bootstrap.php b/module/VuFindHttp/tests/unit-tests/bootstrap.php new file mode 100644 index 0000000000000000000000000000000000000000..fcc2fd66998d8ca46f44ac734d8ee9bd9fbb76b9 --- /dev/null +++ b/module/VuFindHttp/tests/unit-tests/bootstrap.php @@ -0,0 +1,23 @@ +<?php + +/** + * Proxy service PHPUnit bootstrap. + * + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @copyright Copyright (C) Villanova University 2011 + * + */ + +require_once('Zend/Loader/AutoloaderFactory.php'); +\Zend\Loader\AutoloaderFactory::factory( + array( + 'Zend\Loader\StandardAutoloader' => array( + 'namespaces' => array( + 'VuFindHttp' => realpath(__DIR__ . '/../../src/VuFindHttp'), + 'VuFindTest' => realpath(__DIR__ . '/src/VuFindTest'), + ), + 'autoregister_zf' => true + ) + ) +); diff --git a/module/VuFindHttp/tests/unit-tests/phpunit.xml b/module/VuFindHttp/tests/unit-tests/phpunit.xml new file mode 100644 index 0000000000000000000000000000000000000000..1cbaaf01a44dd4b15defbdbccc0ca8f4795c4722 --- /dev/null +++ b/module/VuFindHttp/tests/unit-tests/phpunit.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<phpunit bootstrap="bootstrap.php"> + <testsuites> + <testsuite name="core-proxy"> + <directory>src</directory> + </testsuite> + </testsuites> + <logging> + <log type="coverage-html" target="../../review/coverage"/> + </logging> + <php> + <includePath>../../vendor/zf2/library</includePath> + </php> +</phpunit> diff --git a/module/VuFindHttp/tests/unit-tests/src/VuFindTest/HttpServiceTest.php b/module/VuFindHttp/tests/unit-tests/src/VuFindTest/HttpServiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6072628503b50584da24887843f6398f1b1d8998 --- /dev/null +++ b/module/VuFindHttp/tests/unit-tests/src/VuFindTest/HttpServiceTest.php @@ -0,0 +1,238 @@ +<?php + +/** + * Proxy service unit test. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Search + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +namespace VuFindTest; + +use VuFindHttp\HttpService as Service; + +/** + * Proxy service unit test. + * + * PHP version 5 + * + * Copyright (C) Villanova University 2010. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * @category Search + * @package Service + * @author David Maus <maus@hab.de> + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://github.com/dmj/vf2-proxy + */ + +class ProxyServiceTest extends \PHPUnit_Framework_TestCase +{ + + protected $local = array('ipv4 localhost' => 'http://localhost', + 'ipv4 loopback' => 'http://127.0.0.1', + 'ipv6 loopback' => 'http://[::1]'); + + /** + * Test GET request with associative array. + * + * @return void + */ + public function testGetWithAssocParams () + { + $service = new Service(); + $adapter = $this->getMock('Zend\Http\Client\Adapter\Test', array('write')); + $adapter->expects($this->once()) + ->method('write') + ->with( + $this->equalTo('GET'), + $this->equalTo( + new \Zend\Uri\Http('http://example.tld?foo=bar&bar%5B0%5D=baz') + ) + ); + $service->setDefaultAdapter($adapter); + $service->get('http://example.tld', array('foo' => 'bar', 'bar' => array('baz'))); + } + + /** + * Test GET request with parameter pairs. + * + * @return void + */ + public function testGetWithParamPairs () + { + $service = new Service(); + $adapter = $this->getMock('Zend\Http\Client\Adapter\Test', array('write')); + $adapter->expects($this->once()) + ->method('write') + ->with( + $this->equalTo('GET'), + $this->equalTo( + new \Zend\Uri\Http('http://example.tld?foo=bar&bar=baz&bar=bar') + ) + ); + $service->setDefaultAdapter($adapter); + $service->get('http://example.tld', array('foo=bar', 'bar=baz', 'bar=bar')); + } + + /** + * Test GET request appends query part. + * + * @return void + */ + public function testGetAppendsQueryPart () + { + $service = new Service(); + $adapter = $this->getMock('Zend\Http\Client\Adapter\Test', array('write')); + $adapter->expects($this->once()) + ->method('write') + ->with( + $this->equalTo('GET'), + $this->equalTo( + new \Zend\Uri\Http('http://example.tld?foo=bar&bar=baz') + ) + ); + $service->setDefaultAdapter($adapter); + $service->get('http://example.tld?foo=bar', array('bar=baz')); + } + + /** + * Test POST request of form data. + * + * @return void + */ + public function testPostForm () + { + $service = new Service(); + $adapter = new \Zend\Http\Client\Adapter\Test(); + $service->setDefaultAdapter($adapter); + $service->postForm('http://example.tld', array('foo=bar')); + } + + /** + * Test POST request.with empty body + * + * @return void + */ + public function testSendPostRequestEmptyBody () + { + $service = new Service(); + $adapter = $this->getMock('Zend\Http\Client\Adapter\Test', array('write')); + $adapter->expects($this->once()) + ->method('write') + ->with( + $this->equalTo('POST'), + $this->equalTo( + new \Zend\Uri\Http('http://example.tld') + ) + ); + $service->setDefaultAdapter($adapter); + $service->post('http://example.tld'); + } + + /** + * Test proxify. + * + * @return void + */ + public function testProxify () + { + $service = new Service( + array( + 'proxy_host' => 'localhost', + 'proxy_port' => '666' + ) + ); + $client = new \Zend\Http\Client('http://example.tld:8080'); + $client = $service->proxify($client); + $adapter = $client->getAdapter(); + $this->assertInstanceOf('Zend\Http\Client\Adapter\Proxy', $adapter); + $config = $adapter->getConfig(); + $this->assertEquals('localhost', $config['proxy_host']); + $this->assertEquals('666', $config['proxy_port']); + } + + /** + * Test no proxify with local address. + * + * @return void + */ + public function testNoProxifyLocal () + { + $service = new Service( + array( + 'proxy_host' => 'localhost', + 'proxy_port' => '666' + ) + ); + foreach ($this->local as $name => $address) { + $client = new \Zend\Http\Client($address); + $client->setAdapter(new \Zend\Http\Client\Adapter\Test()); + $client = $service->proxify($client); + $this->assertInstanceOf( + 'Zend\Http\Client\Adapter\Test', + $client->getAdapter(), + sprintf('Failed to proxify %s: %s', $name, $address) + ); + } + } + + /** + * Test for runtime exception. + * + * @expectedException \VuFindHttp\Exception\RuntimeException + * + * @return void + */ + public function testRuntimeException () + { + $service = new Service(); + $service->get('http://example.tld'); + } + + /** + * Test isAssocArray with mixed keys. + * + * @return void + */ + public function testIsAssocArrayWithMixedKeys () + { + $arr = array(); + $arr[] = 'foo'; + $arr['foo'] = 'bar'; + $this->assertTrue(Service::isAssocParams($arr)); + } + +} \ No newline at end of file