Merged FastCGI implementation into 'master'.
This commit is contained in:
commit
5bf85701dc
|
@ -0,0 +1,323 @@
|
||||||
|
|
||||||
|
CREATIVE COMMONS Attribution-NonCommercial-NoDerivs 3.0 Unported
|
||||||
|
|
||||||
|
Summary
|
||||||
|
|
||||||
|
Attribution — You must attribute the work in the manner specified
|
||||||
|
by the author or licensor (but not in any way that suggests that they
|
||||||
|
endorse you or your use of the work).
|
||||||
|
|
||||||
|
Noncommercial — You may not use this work for commercial purposes.
|
||||||
|
|
||||||
|
No Derivative Works — You may not alter, transform, or build upon
|
||||||
|
this work.
|
||||||
|
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL
|
||||||
|
SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT
|
||||||
|
RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS"
|
||||||
|
BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION
|
||||||
|
PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE.
|
||||||
|
|
||||||
|
License
|
||||||
|
|
||||||
|
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
|
||||||
|
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
|
||||||
|
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN
|
||||||
|
AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
|
||||||
|
|
||||||
|
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
|
||||||
|
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE
|
||||||
|
MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
|
||||||
|
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
|
||||||
|
CONDITIONS.
|
||||||
|
|
||||||
|
1. Definitions
|
||||||
|
|
||||||
|
"Adaptation" means a work based upon the Work, or upon the Work and other
|
||||||
|
pre-existing works, such as a translation, adaptation, derivative work,
|
||||||
|
arrangement of music or other alterations of a literary or artistic work,
|
||||||
|
or phonogram or performance and includes cinematographic adaptations or
|
||||||
|
any other form in which the Work may be recast, transformed, or adapted
|
||||||
|
including in any form recognizably derived from the original, except that
|
||||||
|
a work that constitutes a Collection will not be considered an Adaptation
|
||||||
|
for the purpose of this License. For the avoidance of doubt, where the
|
||||||
|
Work is a musical work, performance or phonogram, the synchronization
|
||||||
|
of the Work in timed-relation with a moving image ("synching") will be
|
||||||
|
considered an Adaptation for the purpose of this License.
|
||||||
|
|
||||||
|
"Collection" means a collection of literary or artistic works, such as
|
||||||
|
encyclopedias and anthologies, or performances, phonograms or broadcasts,
|
||||||
|
or other works or subject matter other than works listed in Section
|
||||||
|
1(f) below, which, by reason of the selection and arrangement of
|
||||||
|
their contents, constitute intellectual creations, in which the Work
|
||||||
|
is included in its entirety in unmodified form along with one or more
|
||||||
|
other contributions, each constituting separate and independent works
|
||||||
|
in themselves, which together are assembled into a collective whole. A
|
||||||
|
work that constitutes a Collection will not be considered an Adaptation
|
||||||
|
(as defined above) for the purposes of this License.
|
||||||
|
|
||||||
|
"Distribute" means to make available to the public the original and
|
||||||
|
copies of the Work through sale or other transfer of ownership.
|
||||||
|
|
||||||
|
"Licensor" means the individual, individuals, entity or entities that
|
||||||
|
offer(s) the Work under the terms of this License.
|
||||||
|
|
||||||
|
"Original Author" means, in the case of a literary or artistic work, the
|
||||||
|
individual, individuals, entity or entities who created the Work or if
|
||||||
|
no individual or entity can be identified, the publisher; and in addition
|
||||||
|
(i) in the case of a performance the actors, singers, musicians, dancers,
|
||||||
|
and other persons who act, sing, deliver, declaim, play in, interpret or
|
||||||
|
otherwise perform literary or artistic works or expressions of folklore;
|
||||||
|
(ii) in the case of a phonogram the producer being the person or legal
|
||||||
|
entity who first fixes the sounds of a performance or other sounds;
|
||||||
|
and, (iii) in the case of broadcasts, the organization that transmits
|
||||||
|
the broadcast.
|
||||||
|
|
||||||
|
"Work" means the literary and/or artistic work offered under the terms
|
||||||
|
of this License including without limitation any production in the
|
||||||
|
literary, scientific and artistic domain, whatever may be the mode or
|
||||||
|
form of its expression including digital form, such as a book, pamphlet
|
||||||
|
and other writing; a lecture, address, sermon or other work of the same
|
||||||
|
nature; a dramatic or dramatico-musical work; a choreographic work
|
||||||
|
or entertainment in dumb show; a musical composition with or without
|
||||||
|
words; a cinematographic work to which are assimilated works expressed
|
||||||
|
by a process analogous to cinematography; a work of drawing, painting,
|
||||||
|
architecture, sculpture, engraving or lithography; a photographic
|
||||||
|
work to which are assimilated works expressed by a process analogous
|
||||||
|
to photography; a work of applied art; an illustration, map, plan,
|
||||||
|
sketch or three-dimensional work relative to geography, topography,
|
||||||
|
architecture or science; a performance; a broadcast; a phonogram;
|
||||||
|
a compilation of data to the extent it is protected as a copyrightable
|
||||||
|
work; or a work performed by a variety or circus performer to the extent
|
||||||
|
it is not otherwise considered a literary or artistic work.
|
||||||
|
|
||||||
|
"You" means an individual or entity exercising rights under this License
|
||||||
|
who has not previously violated the terms of this License with respect
|
||||||
|
to the Work, or who has received express permission from the Licensor
|
||||||
|
to exercise rights under this License despite a previous violation.
|
||||||
|
|
||||||
|
"Publicly Perform" means to perform public recitations of the Work and
|
||||||
|
to communicate to the public those public recitations, by any means
|
||||||
|
or process, including by wire or wireless means or public digital
|
||||||
|
performances; to make available to the public Works in such a way that
|
||||||
|
members of the public may access these Works from a place and at a place
|
||||||
|
individually chosen by them; to perform the Work to the public by any
|
||||||
|
means or process and the communication to the public of the performances
|
||||||
|
of the Work, including by public digital performance; to broadcast and
|
||||||
|
rebroadcast the Work by any means including signs, sounds or images.
|
||||||
|
|
||||||
|
"Reproduce" means to make copies of the Work by any means including
|
||||||
|
without limitation by sound or visual recordings and the right of fixation
|
||||||
|
and reproducing fixations of the Work, including storage of a protected
|
||||||
|
performance or phonogram in digital form or other electronic medium.
|
||||||
|
|
||||||
|
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
|
||||||
|
limit, or restrict any uses free from copyright or rights arising from
|
||||||
|
limitations or exceptions that are provided for in connection with the
|
||||||
|
copyright protection under copyright law or other applicable laws.
|
||||||
|
|
||||||
|
3. License Grant. Subject to the terms and conditions of this License,
|
||||||
|
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
|
||||||
|
perpetual (for the duration of the applicable copyright) license to
|
||||||
|
exercise the rights in the Work as stated below:
|
||||||
|
|
||||||
|
to Reproduce the Work, to incorporate the Work into one or more
|
||||||
|
Collections, and
|
||||||
|
to Reproduce the Work as incorporated in the Collections;
|
||||||
|
and,
|
||||||
|
to Distribute and Publicly Perform the Work including as incorporated
|
||||||
|
in Collections.
|
||||||
|
|
||||||
|
The above rights may be exercised in all media and formats whether
|
||||||
|
now known or hereafter devised. The above rights include the right to
|
||||||
|
make such modifications as are technically necessary to exercise the
|
||||||
|
rights in other media and formats, but otherwise you have no rights to
|
||||||
|
make Adaptations. Subject to 8(f), all rights not expressly granted by
|
||||||
|
Licensor are hereby reserved, including but not limited to the rights
|
||||||
|
set forth in Section 4(d).
|
||||||
|
|
||||||
|
4. Restrictions. The license granted in Section 3 above is expressly
|
||||||
|
made subject to and limited by the following restrictions:
|
||||||
|
|
||||||
|
You may Distribute or Publicly Perform the Work only under the terms
|
||||||
|
of this License. You must include a copy of, or the Uniform Resource
|
||||||
|
Identifier (URI) for, this License with every copy of the Work You
|
||||||
|
Distribute or Publicly Perform. You may not offer or impose any terms on
|
||||||
|
the Work that restrict the terms of this License or the ability of the
|
||||||
|
recipient of the Work to exercise the rights granted to that recipient
|
||||||
|
under the terms of the License. You may not sublicense the Work. You
|
||||||
|
must keep intact all notices that refer to this License and to the
|
||||||
|
disclaimer of warranties with every copy of the Work You Distribute
|
||||||
|
or Publicly Perform. When You Distribute or Publicly Perform the Work,
|
||||||
|
You may not impose any effective technological measures on the Work that
|
||||||
|
restrict the ability of a recipient of the Work from You to exercise the
|
||||||
|
rights granted to that recipient under the terms of the License. This
|
||||||
|
Section 4(a) applies to the Work as incorporated in a Collection, but
|
||||||
|
this does not require the Collection apart from the Work itself to be
|
||||||
|
made subject to the terms of this License. If You create a Collection,
|
||||||
|
upon notice from any Licensor You must, to the extent practicable, remove
|
||||||
|
from the Collection any credit as required by Section 4(c), as requested.
|
||||||
|
|
||||||
|
You may not exercise any of the rights granted to You in Section 3
|
||||||
|
above in any manner that is primarily intended for or directed toward
|
||||||
|
commercial advantage or private monetary compensation. The exchange of
|
||||||
|
the Work for other copyrighted works by means of digital file-sharing or
|
||||||
|
otherwise shall not be considered to be intended for or directed toward
|
||||||
|
commercial advantage or private monetary compensation, provided there is
|
||||||
|
no payment of any monetary compensation in connection with the exchange
|
||||||
|
of copyrighted works.
|
||||||
|
|
||||||
|
If You Distribute, or Publicly Perform the Work or Collections, You must,
|
||||||
|
unless a request has been made pursuant to Section 4(a), keep intact all
|
||||||
|
copyright notices for the Work and provide, reasonable to the medium
|
||||||
|
or means You are utilizing: (i) the name of the Original Author (or
|
||||||
|
pseudonym, if applicable) if supplied, and/or if the Original Author
|
||||||
|
and/or Licensor designate another party or parties (e.g., a sponsor
|
||||||
|
institute, publishing entity, journal) for attribution ("Attribution
|
||||||
|
Parties") in Licensor's copyright notice, terms of service or by other
|
||||||
|
reasonable means, the name of such party or parties; (ii) the title of
|
||||||
|
the Work if supplied; (iii) to the extent reasonably practicable, the
|
||||||
|
URI, if any, that Licensor specifies to be associated with the Work,
|
||||||
|
unless such URI does not refer to the copyright notice or licensing
|
||||||
|
information for the Work. The credit required by this Section 4(c)
|
||||||
|
may be implemented in any reasonable manner; provided, however, that
|
||||||
|
in the case of a Collection, at a minimum such credit will appear,
|
||||||
|
if a credit for all contributing authors of Collection appears, then
|
||||||
|
as part of these credits and in a manner at least as prominent as the
|
||||||
|
credits for the other contributing authors. For the avoidance of doubt,
|
||||||
|
You may only use the credit required by this Section for the purpose of
|
||||||
|
attribution in the manner set out above and, by exercising Your rights
|
||||||
|
under this License, You may not implicitly or explicitly assert or imply
|
||||||
|
any connection with, sponsorship or endorsement by the Original Author,
|
||||||
|
Licensor and/or Attribution Parties, as appropriate, of You or Your use
|
||||||
|
of the Work, without the separate, express prior written permission of
|
||||||
|
the Original Author, Licensor and/or Attribution Parties.
|
||||||
|
|
||||||
|
For the avoidance of doubt:
|
||||||
|
|
||||||
|
Non-waivable Compulsory License Schemes. In those jurisdictions
|
||||||
|
in which the right to collect royalties through any statutory or
|
||||||
|
compulsory licensing scheme cannot be waived, the Licensor reserves
|
||||||
|
the exclusive right to collect such royalties for any exercise by
|
||||||
|
You of the rights granted under this License;
|
||||||
|
|
||||||
|
Waivable Compulsory License Schemes. In those jurisdictions in which
|
||||||
|
the right to collect royalties through any statutory or compulsory
|
||||||
|
licensing scheme can be waived, the Licensor reserves the exclusive
|
||||||
|
right to collect such royalties for any exercise by You of the rights
|
||||||
|
granted under this License if Your exercise of such rights is for
|
||||||
|
a purpose or use which is otherwise than noncommercial as permitted
|
||||||
|
under Section 4(b) and otherwise waives the right to collect royalties
|
||||||
|
through any statutory or compulsory licensing scheme; and,
|
||||||
|
|
||||||
|
Voluntary License Schemes. The Licensor reserves the right to collect
|
||||||
|
royalties, whether individually or, in the event that the Licensor is
|
||||||
|
a member of a collecting society that administers voluntary licensing
|
||||||
|
schemes, via that society, from any exercise by You of the rights
|
||||||
|
granted under this License that is for a purpose or use which is
|
||||||
|
otherwise than noncommercial as permitted under Section 4(b).
|
||||||
|
|
||||||
|
Except as otherwise agreed in writing by the Licensor or as may be
|
||||||
|
otherwise permitted by applicable law, if You Reproduce, Distribute or
|
||||||
|
Publicly Perform the Work either by itself or as part of any Collections,
|
||||||
|
You must not distort, mutilate, modify or take other derogatory action in
|
||||||
|
relation to the Work which would be prejudicial to the Original Author's
|
||||||
|
honor or reputation.
|
||||||
|
|
||||||
|
5. Representations, Warranties and Disclaimer
|
||||||
|
|
||||||
|
UNLESS OTHERWISE MUTUALLY AGREED BY THE PARTIES IN WRITING, LICENSOR
|
||||||
|
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
|
||||||
|
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
|
||||||
|
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
|
||||||
|
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
|
||||||
|
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
|
||||||
|
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
|
||||||
|
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
|
||||||
|
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
|
||||||
|
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR
|
||||||
|
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
7. Termination
|
||||||
|
|
||||||
|
This License and the rights granted hereunder will terminate automatically
|
||||||
|
upon any breach by You of the terms of this License. Individuals or
|
||||||
|
entities who have received Collections from You under this License,
|
||||||
|
however, will not have their licenses terminated provided such individuals
|
||||||
|
or entities remain in full compliance with those licenses. Sections 1,
|
||||||
|
2, 5, 6, 7, and 8 will survive any termination of this License.
|
||||||
|
|
||||||
|
Subject to the above terms and conditions, the license granted here
|
||||||
|
is perpetual (for the duration of the applicable copyright in the
|
||||||
|
Work). Notwithstanding the above, Licensor reserves the right to release
|
||||||
|
the Work under different license terms or to stop distributing the Work
|
||||||
|
at any time; provided, however that any such election will not serve to
|
||||||
|
withdraw this License (or any other license that has been, or is required
|
||||||
|
to be, granted under the terms of this License), and this License will
|
||||||
|
continue in full force and effect unless terminated as stated above.
|
||||||
|
|
||||||
|
8. Miscellaneous
|
||||||
|
|
||||||
|
Each time You Distribute or Publicly Perform the Work or a Collection,
|
||||||
|
the Licensor offers to the recipient a license to the Work on the same
|
||||||
|
terms and conditions as the license granted to You under this License.
|
||||||
|
|
||||||
|
If any provision of this License is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this License, and without further action
|
||||||
|
by the parties to this agreement, such provision shall be reformed to
|
||||||
|
the minimum extent necessary to make such provision valid and enforceable.
|
||||||
|
|
||||||
|
No term or provision of this License shall be deemed waived and no breach
|
||||||
|
consented to unless such waiver or consent shall be in writing and signed
|
||||||
|
by the party to be charged with such waiver or consent.
|
||||||
|
|
||||||
|
This License constitutes the entire agreement between the parties
|
||||||
|
with respect to the Work licensed here. There are no understandings,
|
||||||
|
agreements or representations with respect to the Work not specified
|
||||||
|
here. Licensor shall not be bound by any additional provisions that may
|
||||||
|
appear in any communication from You. This License may not be modified
|
||||||
|
without the mutual written agreement of the Licensor and You.
|
||||||
|
|
||||||
|
The rights granted under, and the subject matter referenced, in this
|
||||||
|
License were drafted utilizing the terminology of the Berne Convention for
|
||||||
|
the Protection of Literary and Artistic Works (as amended on September 28,
|
||||||
|
1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996,
|
||||||
|
the WIPO Performances and Phonograms Treaty of 1996 and the Universal
|
||||||
|
Copyright Convention (as revised on July 24, 1971). These rights and
|
||||||
|
subject matter take effect in the relevant jurisdiction in which the
|
||||||
|
License terms are sought to be enforced according to the corresponding
|
||||||
|
provisions of the implementation of those treaty provisions in the
|
||||||
|
applicable national law. If the standard suite of rights granted under
|
||||||
|
applicable copyright law includes additional rights not granted under
|
||||||
|
this License, such additional rights are deemed to be included in the
|
||||||
|
License; this License is not intended to restrict the license of any
|
||||||
|
rights under applicable law.
|
||||||
|
|
||||||
|
Creative Commons Notice
|
||||||
|
|
||||||
|
Creative Commons is not a party to this License, and makes no warranty
|
||||||
|
whatsoever in connection with the Work. Creative Commons will not
|
||||||
|
be liable to You or any party on any legal theory for any damages
|
||||||
|
whatsoever, including without limitation any general, special,
|
||||||
|
incidental or consequential damages arising in connection to this
|
||||||
|
license. Notwithstanding the foregoing two (2) sentences, if Creative
|
||||||
|
Commons has expressly identified itself as the Licensor hereunder,
|
||||||
|
it shall have all rights and obligations of Licensor.
|
||||||
|
|
||||||
|
Except for the limited purpose of indicating to the public that the Work
|
||||||
|
is licensed under the CCPL, Creative Commons does not authorize the use by
|
||||||
|
either party of the trademark "Creative Commons" or any related trademark
|
||||||
|
or logo of Creative Commons without the prior written consent of Creative
|
||||||
|
Commons. Any permitted use will be in compliance with Creative Commons'
|
||||||
|
then-current trademark usage guidelines, as may be published on its
|
||||||
|
website or otherwise made available upon request from time to time. For
|
||||||
|
the avoidance of doubt, this trademark restriction does not form part
|
||||||
|
of this License.
|
||||||
|
|
||||||
|
Creative Commons may be contacted at http://creativecommons.org/.
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>fcgi-core</artifactId>
|
||||||
|
<name>Jetty :: FastCGI :: Core</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<bundle-symbolic-name>${project.groupId}.core</bundle-symbolic-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-io</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,136 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi;
|
||||||
|
|
||||||
|
public class FCGI
|
||||||
|
{
|
||||||
|
private FCGI()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Role
|
||||||
|
{
|
||||||
|
RESPONDER(1), AUTHORIZER(2), FILTER(3);
|
||||||
|
|
||||||
|
public static Role from(int code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return RESPONDER;
|
||||||
|
case 2:
|
||||||
|
return AUTHORIZER;
|
||||||
|
case 3:
|
||||||
|
return FILTER;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
private Role(int code)
|
||||||
|
{
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FrameType
|
||||||
|
{
|
||||||
|
BEGIN_REQUEST(1),
|
||||||
|
ABORT_REQUEST(2),
|
||||||
|
END_REQUEST(3),
|
||||||
|
PARAMS(4),
|
||||||
|
STDIN(5),
|
||||||
|
STDOUT(6),
|
||||||
|
STDERR(7),
|
||||||
|
DATA(8),
|
||||||
|
GET_VALUES(9),
|
||||||
|
GET_VALUES_RESULT(10);
|
||||||
|
|
||||||
|
public static FrameType from(int code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return BEGIN_REQUEST;
|
||||||
|
case 2:
|
||||||
|
return ABORT_REQUEST;
|
||||||
|
case 3:
|
||||||
|
return END_REQUEST;
|
||||||
|
case 4:
|
||||||
|
return PARAMS;
|
||||||
|
case 5:
|
||||||
|
return STDIN;
|
||||||
|
case 6:
|
||||||
|
return STDOUT;
|
||||||
|
case 7:
|
||||||
|
return STDERR;
|
||||||
|
case 8:
|
||||||
|
return DATA;
|
||||||
|
case 9:
|
||||||
|
return GET_VALUES;
|
||||||
|
case 10:
|
||||||
|
return GET_VALUES_RESULT;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final int code;
|
||||||
|
|
||||||
|
private FrameType(int code)
|
||||||
|
{
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StreamType
|
||||||
|
{
|
||||||
|
STD_IN, STD_OUT, STD_ERR
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Headers
|
||||||
|
{
|
||||||
|
public static final String AUTH_TYPE = "AUTH_TYPE";
|
||||||
|
public static final String CONTENT_LENGTH = "CONTENT_LENGTH";
|
||||||
|
public static final String CONTENT_TYPE = "CONTENT_TYPE";
|
||||||
|
public static final String DOCUMENT_ROOT = "DOCUMENT_ROOT";
|
||||||
|
public static final String DOCUMENT_URI = "DOCUMENT_URI";
|
||||||
|
public static final String GATEWAY_INTERFACE = "GATEWAY_INTERFACE";
|
||||||
|
public static final String HTTPS = "HTTPS";
|
||||||
|
public static final String PATH_INFO = "PATH_INFO";
|
||||||
|
public static final String QUERY_STRING = "QUERY_STRING";
|
||||||
|
public static final String REMOTE_ADDR = "REMOTE_ADDR";
|
||||||
|
public static final String REMOTE_PORT = "REMOTE_PORT";
|
||||||
|
public static final String REQUEST_METHOD = "REQUEST_METHOD";
|
||||||
|
public static final String REQUEST_URI = "REQUEST_URI";
|
||||||
|
public static final String SCRIPT_FILENAME = "SCRIPT_FILENAME";
|
||||||
|
public static final String SCRIPT_NAME = "SCRIPT_NAME";
|
||||||
|
public static final String SERVER_ADDR = "SERVER_ADDR";
|
||||||
|
public static final String SERVER_NAME = "SERVER_NAME";
|
||||||
|
public static final String SERVER_PORT = "SERVER_PORT";
|
||||||
|
public static final String SERVER_PROTOCOL = "SERVER_PROTOCOL";
|
||||||
|
public static final String SERVER_SOFTWARE = "SERVER_SOFTWARE";
|
||||||
|
|
||||||
|
private Headers()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,166 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.generator;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
|
public class ClientGenerator extends Generator
|
||||||
|
{
|
||||||
|
// To keep the algorithm simple, and given that the max length of a
|
||||||
|
// frame is 0xFF_FF we allow the max length of a name (or value) to be
|
||||||
|
// 0x7F_FF - 4 (the 4 is to make room for the name (or value) length).
|
||||||
|
public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4;
|
||||||
|
|
||||||
|
public ClientGenerator(ByteBufferPool byteBufferPool)
|
||||||
|
{
|
||||||
|
super(byteBufferPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result generateRequestHeaders(int request, HttpFields fields, Callback callback)
|
||||||
|
{
|
||||||
|
request &= 0xFF_FF;
|
||||||
|
|
||||||
|
Charset utf8 = Charset.forName("UTF-8");
|
||||||
|
List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
|
||||||
|
int fieldsLength = 0;
|
||||||
|
for (HttpField field : fields)
|
||||||
|
{
|
||||||
|
String name = field.getName();
|
||||||
|
byte[] nameBytes = name.getBytes(utf8);
|
||||||
|
if (nameBytes.length > MAX_PARAM_LENGTH)
|
||||||
|
throw new IllegalArgumentException("Field name " + name + " exceeds max length " + MAX_PARAM_LENGTH);
|
||||||
|
bytes.add(nameBytes);
|
||||||
|
|
||||||
|
String value = field.getValue();
|
||||||
|
byte[] valueBytes = value.getBytes(utf8);
|
||||||
|
if (valueBytes.length > MAX_PARAM_LENGTH)
|
||||||
|
throw new IllegalArgumentException("Field value " + value + " exceeds max length " + MAX_PARAM_LENGTH);
|
||||||
|
bytes.add(valueBytes);
|
||||||
|
|
||||||
|
int nameLength = nameBytes.length;
|
||||||
|
fieldsLength += bytesForLength(nameLength);
|
||||||
|
|
||||||
|
int valueLength = valueBytes.length;
|
||||||
|
fieldsLength += bytesForLength(valueLength);
|
||||||
|
|
||||||
|
fieldsLength += nameLength;
|
||||||
|
fieldsLength += valueLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Worst case FCGI_PARAMS frame: long name + long value - both of MAX_PARAM_LENGTH
|
||||||
|
int maxCapacity = 4 + 4 + 2 * MAX_PARAM_LENGTH;
|
||||||
|
|
||||||
|
// One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS
|
||||||
|
|
||||||
|
ByteBuffer beginRequestBuffer = byteBufferPool.acquire(16, false);
|
||||||
|
BufferUtil.clearToFill(beginRequestBuffer);
|
||||||
|
Result result = new Result(byteBufferPool, callback);
|
||||||
|
result = result.append(beginRequestBuffer, true);
|
||||||
|
|
||||||
|
// Generate the FCGI_BEGIN_REQUEST frame
|
||||||
|
beginRequestBuffer.putInt(0x01_01_00_00 + request);
|
||||||
|
beginRequestBuffer.putInt(0x00_08_00_00);
|
||||||
|
// Hardcode RESPONDER role and KEEP_ALIVE flag
|
||||||
|
beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L);
|
||||||
|
beginRequestBuffer.flip();
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
while (fieldsLength > 0)
|
||||||
|
{
|
||||||
|
int capacity = 8 + Math.min(maxCapacity, fieldsLength);
|
||||||
|
ByteBuffer buffer = byteBufferPool.acquire(capacity, true);
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
result = result.append(buffer, true);
|
||||||
|
|
||||||
|
// Generate the FCGI_PARAMS frame
|
||||||
|
buffer.putInt(0x01_04_00_00 + request);
|
||||||
|
buffer.putShort((short)0);
|
||||||
|
buffer.putShort((short)0);
|
||||||
|
capacity -= 8;
|
||||||
|
|
||||||
|
int length = 0;
|
||||||
|
while (index < bytes.size())
|
||||||
|
{
|
||||||
|
byte[] nameBytes = bytes.get(index);
|
||||||
|
int nameLength = nameBytes.length;
|
||||||
|
byte[] valueBytes = bytes.get(index + 1);
|
||||||
|
int valueLength = valueBytes.length;
|
||||||
|
|
||||||
|
int required = bytesForLength(nameLength) + bytesForLength(valueLength) + nameLength + valueLength;
|
||||||
|
if (required > capacity)
|
||||||
|
break;
|
||||||
|
|
||||||
|
putParamLength(buffer, nameLength);
|
||||||
|
putParamLength(buffer, valueLength);
|
||||||
|
buffer.put(nameBytes);
|
||||||
|
buffer.put(valueBytes);
|
||||||
|
|
||||||
|
length += required;
|
||||||
|
fieldsLength -= required;
|
||||||
|
capacity -= required;
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.putShort(4, (short)length);
|
||||||
|
buffer.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ByteBuffer lastParamsBuffer = byteBufferPool.acquire(8, false);
|
||||||
|
BufferUtil.clearToFill(lastParamsBuffer);
|
||||||
|
result = result.append(lastParamsBuffer, true);
|
||||||
|
|
||||||
|
// Generate the last FCGI_PARAMS frame
|
||||||
|
lastParamsBuffer.putInt(0x01_04_00_00 + request);
|
||||||
|
lastParamsBuffer.putInt(0x00_00_00_00);
|
||||||
|
lastParamsBuffer.flip();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int putParamLength(ByteBuffer buffer, int length)
|
||||||
|
{
|
||||||
|
int result = bytesForLength(length);
|
||||||
|
if (result == 4)
|
||||||
|
buffer.putInt(length | 0x80_00_00_00);
|
||||||
|
else
|
||||||
|
buffer.put((byte)length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bytesForLength(int length)
|
||||||
|
{
|
||||||
|
return length > 127 ? 4 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result generateRequestContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
|
{
|
||||||
|
return generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDIN);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.generator;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.ConcurrentArrayQueue;
|
||||||
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class Flusher
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(Flusher.class);
|
||||||
|
|
||||||
|
private final Queue<Generator.Result> queue = new ConcurrentArrayQueue<>();
|
||||||
|
private final IteratingCallback flushCallback = new FlushCallback();
|
||||||
|
private final EndPoint endPoint;
|
||||||
|
|
||||||
|
public Flusher(EndPoint endPoint)
|
||||||
|
{
|
||||||
|
this.endPoint = endPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flush(Generator.Result... results)
|
||||||
|
{
|
||||||
|
for (Generator.Result result : results)
|
||||||
|
queue.offer(result);
|
||||||
|
flushCallback.iterate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
flush(new ShutdownResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FlushCallback extends IteratingCallback
|
||||||
|
{
|
||||||
|
private Generator.Result active;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Action process() throws Exception
|
||||||
|
{
|
||||||
|
// Look if other writes are needed.
|
||||||
|
Generator.Result result = queue.poll();
|
||||||
|
if (result == null)
|
||||||
|
{
|
||||||
|
// No more writes to do, return.
|
||||||
|
return Action.IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to gather another result.
|
||||||
|
// Most often there is another result in the
|
||||||
|
// queue so this is a real optimization because
|
||||||
|
// it sends both results in just one TCP packet.
|
||||||
|
Generator.Result other = queue.poll();
|
||||||
|
if (other != null)
|
||||||
|
result = result.join(other);
|
||||||
|
|
||||||
|
active = result;
|
||||||
|
ByteBuffer[] buffers = result.getByteBuffers();
|
||||||
|
endPoint.write(this, buffers);
|
||||||
|
return Action.SCHEDULED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void completed()
|
||||||
|
{
|
||||||
|
// We never return Action.SUCCEEDED, so this method is never called.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
if (active != null)
|
||||||
|
active.succeeded();
|
||||||
|
active = null;
|
||||||
|
super.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
if (active != null)
|
||||||
|
active.failed(x);
|
||||||
|
active = null;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
Generator.Result result = queue.poll();
|
||||||
|
if (result == null)
|
||||||
|
break;
|
||||||
|
result.failed(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ShutdownResult extends Generator.Result
|
||||||
|
{
|
||||||
|
private ShutdownResult()
|
||||||
|
{
|
||||||
|
super(null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown()
|
||||||
|
{
|
||||||
|
LOG.debug("Shutting down {}", endPoint);
|
||||||
|
endPoint.shutdownOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.generator;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
|
public class Generator
|
||||||
|
{
|
||||||
|
public static final int MAX_CONTENT_LENGTH = 0xFF_FF;
|
||||||
|
|
||||||
|
protected final ByteBufferPool byteBufferPool;
|
||||||
|
|
||||||
|
public Generator(ByteBufferPool byteBufferPool)
|
||||||
|
{
|
||||||
|
this.byteBufferPool = byteBufferPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Result generateContent(int id, ByteBuffer content, boolean recycle, boolean lastContent, Callback callback, FCGI.FrameType frameType)
|
||||||
|
{
|
||||||
|
id &= 0xFF_FF;
|
||||||
|
|
||||||
|
int contentLength = content == null ? 0 : content.remaining();
|
||||||
|
Result result = new Result(byteBufferPool, callback);
|
||||||
|
|
||||||
|
while (contentLength > 0 || lastContent)
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = byteBufferPool.acquire(8, false);
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
result = result.append(buffer, true);
|
||||||
|
|
||||||
|
// Generate the frame header
|
||||||
|
buffer.put((byte)0x01);
|
||||||
|
buffer.put((byte)frameType.code);
|
||||||
|
buffer.putShort((short)id);
|
||||||
|
int length = Math.min(MAX_CONTENT_LENGTH, contentLength);
|
||||||
|
buffer.putShort((short)length);
|
||||||
|
buffer.putShort((short)0);
|
||||||
|
buffer.flip();
|
||||||
|
|
||||||
|
if (contentLength == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Slice the content to avoid copying
|
||||||
|
int limit = content.limit();
|
||||||
|
content.limit(content.position() + length);
|
||||||
|
ByteBuffer slice = content.slice();
|
||||||
|
// Don't recycle the slice
|
||||||
|
result = result.append(slice, false);
|
||||||
|
content.position(content.limit());
|
||||||
|
content.limit(limit);
|
||||||
|
contentLength -= length;
|
||||||
|
// Recycle the content buffer if needed
|
||||||
|
if (recycle && contentLength == 0)
|
||||||
|
result = result.append(content, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Result implements Callback
|
||||||
|
{
|
||||||
|
private final List<Callback> callbacks = new ArrayList<>(2);
|
||||||
|
private final List<ByteBuffer> buffers = new ArrayList<>(8);
|
||||||
|
private final List<Boolean> recycles = new ArrayList<>(8);
|
||||||
|
private final ByteBufferPool byteBufferPool;
|
||||||
|
|
||||||
|
public Result(ByteBufferPool byteBufferPool, Callback callback)
|
||||||
|
{
|
||||||
|
this.byteBufferPool = byteBufferPool;
|
||||||
|
this.callbacks.add(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result append(ByteBuffer buffer, boolean recycle)
|
||||||
|
{
|
||||||
|
if (buffer != null)
|
||||||
|
{
|
||||||
|
buffers.add(buffer);
|
||||||
|
recycles.add(recycle);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result join(Result that)
|
||||||
|
{
|
||||||
|
callbacks.addAll(that.callbacks);
|
||||||
|
buffers.addAll(that.buffers);
|
||||||
|
recycles.addAll(that.recycles);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer[] getByteBuffers()
|
||||||
|
{
|
||||||
|
return buffers.toArray(new ByteBuffer[buffers.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
recycle();
|
||||||
|
for (int i = 0; i < callbacks.size(); ++i)
|
||||||
|
{
|
||||||
|
Callback callback = callbacks.get(i);
|
||||||
|
if (callback != null)
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("ForLoopReplaceableByForEach")
|
||||||
|
public void failed(Throwable x)
|
||||||
|
{
|
||||||
|
recycle();
|
||||||
|
for (int i = 0; i < callbacks.size(); ++i)
|
||||||
|
{
|
||||||
|
Callback callback = callbacks.get(i);
|
||||||
|
if (callback != null)
|
||||||
|
callback.failed(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void recycle()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < buffers.size(); ++i)
|
||||||
|
{
|
||||||
|
ByteBuffer buffer = buffers.get(i);
|
||||||
|
if (recycles.get(i))
|
||||||
|
byteBufferPool.release(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.generator;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
|
public class ServerGenerator extends Generator
|
||||||
|
{
|
||||||
|
private static final byte[] STATUS = new byte[]{'S', 't', 'a', 't', 'u', 's'};
|
||||||
|
private static final byte[] COLON = new byte[]{':', ' '};
|
||||||
|
private static final byte[] EOL = new byte[]{'\r', '\n'};
|
||||||
|
|
||||||
|
public ServerGenerator(ByteBufferPool byteBufferPool)
|
||||||
|
{
|
||||||
|
super(byteBufferPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result generateResponseHeaders(int request, int code, String reason, HttpFields fields, Callback callback)
|
||||||
|
{
|
||||||
|
request &= 0xFF_FF;
|
||||||
|
|
||||||
|
Charset utf8 = Charset.forName("UTF-8");
|
||||||
|
List<byte[]> bytes = new ArrayList<>(fields.size() * 2);
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
// Special 'Status' header
|
||||||
|
bytes.add(STATUS);
|
||||||
|
length += STATUS.length + COLON.length;
|
||||||
|
if (reason == null)
|
||||||
|
reason = HttpStatus.getMessage(code);
|
||||||
|
byte[] responseBytes = (code + " " + reason).getBytes(utf8);
|
||||||
|
bytes.add(responseBytes);
|
||||||
|
length += responseBytes.length + EOL.length;
|
||||||
|
|
||||||
|
// Other headers
|
||||||
|
for (HttpField field : fields)
|
||||||
|
{
|
||||||
|
String name = field.getName();
|
||||||
|
byte[] nameBytes = name.getBytes(utf8);
|
||||||
|
bytes.add(nameBytes);
|
||||||
|
|
||||||
|
String value = field.getValue();
|
||||||
|
byte[] valueBytes = value.getBytes(utf8);
|
||||||
|
bytes.add(valueBytes);
|
||||||
|
|
||||||
|
length += nameBytes.length + COLON.length;
|
||||||
|
length += valueBytes.length + EOL.length;
|
||||||
|
}
|
||||||
|
// End of headers
|
||||||
|
length += EOL.length;
|
||||||
|
|
||||||
|
final ByteBuffer buffer = byteBufferPool.acquire(length, true);
|
||||||
|
BufferUtil.clearToFill(buffer);
|
||||||
|
|
||||||
|
for (int i = 0; i < bytes.size(); i += 2)
|
||||||
|
buffer.put(bytes.get(i)).put(COLON).put(bytes.get(i + 1)).put(EOL);
|
||||||
|
buffer.put(EOL);
|
||||||
|
|
||||||
|
buffer.flip();
|
||||||
|
|
||||||
|
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
|
{
|
||||||
|
Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT);
|
||||||
|
if (lastContent)
|
||||||
|
{
|
||||||
|
// Generate the FCGI_END_REQUEST
|
||||||
|
request &= 0xFF_FF;
|
||||||
|
ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false);
|
||||||
|
BufferUtil.clearToFill(endRequestBuffer);
|
||||||
|
endRequestBuffer.putInt(0x01_03_00_00 + request);
|
||||||
|
endRequestBuffer.putInt(0x00_08_00_00);
|
||||||
|
endRequestBuffer.putLong(0x00L);
|
||||||
|
endRequestBuffer.flip();
|
||||||
|
result = result.append(endRequestBuffer, true);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
|
||||||
|
public class BeginRequestContentParser extends ContentParser
|
||||||
|
{
|
||||||
|
private final ServerParser.Listener listener;
|
||||||
|
private State state = State.ROLE;
|
||||||
|
private int cursor;
|
||||||
|
private int role;
|
||||||
|
private int flags;
|
||||||
|
|
||||||
|
public BeginRequestContentParser(HeaderParser headerParser, ServerParser.Listener listener)
|
||||||
|
{
|
||||||
|
super(headerParser);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case ROLE:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 2)
|
||||||
|
{
|
||||||
|
role = buffer.getShort();
|
||||||
|
state = State.FLAGS;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.ROLE_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ROLE_BYTES:
|
||||||
|
{
|
||||||
|
int halfShort = buffer.get() & 0xFF;
|
||||||
|
role = (role << 8) + halfShort;
|
||||||
|
if (++cursor == 2)
|
||||||
|
state = State.FLAGS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case FLAGS:
|
||||||
|
{
|
||||||
|
flags = buffer.get() & 0xFF;
|
||||||
|
state = State.RESERVED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RESERVED:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 5)
|
||||||
|
{
|
||||||
|
buffer.position(buffer.position() + 5);
|
||||||
|
onStart();
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.RESERVED_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case RESERVED_BYTES:
|
||||||
|
{
|
||||||
|
buffer.get();
|
||||||
|
if (++cursor == 5)
|
||||||
|
{
|
||||||
|
onStart();
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStart()
|
||||||
|
{
|
||||||
|
listener.onStart(getRequest(), FCGI.Role.from(role), flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
state = State.ROLE;
|
||||||
|
cursor = 0;
|
||||||
|
role = 0;
|
||||||
|
flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
ROLE, ROLE_BYTES, FLAGS, RESERVED, RESERVED_BYTES
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
|
||||||
|
public class ClientParser extends Parser
|
||||||
|
{
|
||||||
|
private final EnumMap<FCGI.FrameType, ContentParser> contentParsers = new EnumMap<>(FCGI.FrameType.class);
|
||||||
|
|
||||||
|
public ClientParser(Listener listener)
|
||||||
|
{
|
||||||
|
ResponseContentParser stdOutParser = new ResponseContentParser(headerParser, listener);
|
||||||
|
contentParsers.put(FCGI.FrameType.STDOUT, stdOutParser);
|
||||||
|
StreamContentParser stdErrParser = new StreamContentParser(headerParser, FCGI.StreamType.STD_ERR, listener);
|
||||||
|
contentParsers.put(FCGI.FrameType.STDERR, stdErrParser);
|
||||||
|
contentParsers.put(FCGI.FrameType.END_REQUEST, new EndRequestContentParser(headerParser, new EndRequestListener(listener, stdOutParser, stdErrParser)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ContentParser findContentParser(FCGI.FrameType frameType)
|
||||||
|
{
|
||||||
|
return contentParsers.get(frameType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener extends Parser.Listener
|
||||||
|
{
|
||||||
|
public void onBegin(int request, int code, String reason);
|
||||||
|
|
||||||
|
public static class Adapter extends Parser.Listener.Adapter implements Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBegin(int request, int code, String reason)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EndRequestListener implements Listener
|
||||||
|
{
|
||||||
|
private final Listener listener;
|
||||||
|
private final StreamContentParser[] streamParsers;
|
||||||
|
|
||||||
|
private EndRequestListener(Listener listener, StreamContentParser... streamParsers)
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
this.streamParsers = streamParsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBegin(int request, int code, String reason)
|
||||||
|
{
|
||||||
|
listener.onBegin(request, code, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
listener.onHeader(request, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
listener.onHeaders(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
listener.onContent(request, stream, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
listener.onEnd(request);
|
||||||
|
for (StreamContentParser streamParser : streamParsers)
|
||||||
|
streamParser.end(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public abstract class ContentParser
|
||||||
|
{
|
||||||
|
private final HeaderParser headerParser;
|
||||||
|
|
||||||
|
protected ContentParser(HeaderParser headerParser)
|
||||||
|
{
|
||||||
|
this.headerParser = headerParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean parse(ByteBuffer buffer);
|
||||||
|
|
||||||
|
public void noContent()
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getRequest()
|
||||||
|
{
|
||||||
|
return headerParser.getRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getContentLength()
|
||||||
|
{
|
||||||
|
return headerParser.getContentLength();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
public class EndRequestContentParser extends ContentParser
|
||||||
|
{
|
||||||
|
private final Parser.Listener listener;
|
||||||
|
private State state = State.APPLICATION;
|
||||||
|
private int cursor;
|
||||||
|
private int application;
|
||||||
|
private int protocol;
|
||||||
|
|
||||||
|
public EndRequestContentParser(HeaderParser headerParser, Parser.Listener listener)
|
||||||
|
{
|
||||||
|
super(headerParser);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case APPLICATION:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 4)
|
||||||
|
{
|
||||||
|
application = buffer.getInt();
|
||||||
|
state = State.PROTOCOL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.APPLICATION_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPLICATION_BYTES:
|
||||||
|
{
|
||||||
|
int quarterInt = buffer.get() & 0xFF;
|
||||||
|
application = (application << 8) + quarterInt;
|
||||||
|
if (++cursor == 4)
|
||||||
|
state = State.PROTOCOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PROTOCOL:
|
||||||
|
{
|
||||||
|
protocol = buffer.get() & 0xFF;
|
||||||
|
state = State.RESERVED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RESERVED:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 3)
|
||||||
|
{
|
||||||
|
buffer.position(buffer.position() + 3);
|
||||||
|
onEnd();
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.APPLICATION_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case RESERVED_BYTES:
|
||||||
|
{
|
||||||
|
buffer.get();
|
||||||
|
if (++cursor == 0)
|
||||||
|
{
|
||||||
|
onEnd();
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onEnd()
|
||||||
|
{
|
||||||
|
// TODO: if protocol != 0, invoke an error callback
|
||||||
|
listener.onEnd(getRequest());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
state = State.APPLICATION;
|
||||||
|
cursor = 0;
|
||||||
|
application = 0;
|
||||||
|
protocol = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
APPLICATION, APPLICATION_BYTES, PROTOCOL, RESERVED, RESERVED_BYTES
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
|
||||||
|
public class HeaderParser
|
||||||
|
{
|
||||||
|
private State state = State.VERSION;
|
||||||
|
private int cursor;
|
||||||
|
private int version;
|
||||||
|
private int type;
|
||||||
|
private int request;
|
||||||
|
private int length;
|
||||||
|
private int padding;
|
||||||
|
|
||||||
|
public boolean parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case VERSION:
|
||||||
|
{
|
||||||
|
version = buffer.get() & 0xFF;
|
||||||
|
state = State.TYPE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case TYPE:
|
||||||
|
{
|
||||||
|
type = buffer.get() & 0xFF;
|
||||||
|
state = State.REQUEST;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case REQUEST:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 2)
|
||||||
|
{
|
||||||
|
request = buffer.getShort() & 0xFF_FF;
|
||||||
|
state = State.LENGTH;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.REQUEST_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case REQUEST_BYTES:
|
||||||
|
{
|
||||||
|
int halfShort = buffer.get() & 0xFF;
|
||||||
|
request = (request << 8) + halfShort;
|
||||||
|
if (++cursor == 2)
|
||||||
|
state = State.LENGTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LENGTH:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 2)
|
||||||
|
{
|
||||||
|
length = buffer.getShort() & 0xFF_FF;
|
||||||
|
state = State.PADDING;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.LENGTH_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LENGTH_BYTES:
|
||||||
|
{
|
||||||
|
int halfShort = buffer.get() & 0xFF;
|
||||||
|
length = (length << 8) + halfShort;
|
||||||
|
if (++cursor == 2)
|
||||||
|
state = State.PADDING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PADDING:
|
||||||
|
{
|
||||||
|
padding = buffer.get() & 0xFF;
|
||||||
|
state = State.RESERVED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RESERVED:
|
||||||
|
{
|
||||||
|
buffer.get();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FCGI.FrameType getFrameType()
|
||||||
|
{
|
||||||
|
return FCGI.FrameType.from(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRequest()
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getContentLength()
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPaddingLength()
|
||||||
|
{
|
||||||
|
return padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void reset()
|
||||||
|
{
|
||||||
|
state = State.VERSION;
|
||||||
|
cursor = 0;
|
||||||
|
version = 0;
|
||||||
|
type = 0;
|
||||||
|
request = 0;
|
||||||
|
length = 0;
|
||||||
|
padding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
VERSION, TYPE, REQUEST, REQUEST_BYTES, LENGTH, LENGTH_BYTES, PADDING, RESERVED
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class ParamsContentParser extends ContentParser
|
||||||
|
{
|
||||||
|
private static final Logger logger = Log.getLogger(ParamsContentParser.class);
|
||||||
|
|
||||||
|
private final ServerParser.Listener listener;
|
||||||
|
private State state = State.LENGTH;
|
||||||
|
private int cursor;
|
||||||
|
private int length;
|
||||||
|
private int nameLength;
|
||||||
|
private int valueLength;
|
||||||
|
private byte[] nameBytes;
|
||||||
|
private byte[] valueBytes;
|
||||||
|
|
||||||
|
public ParamsContentParser(HeaderParser headerParser, ServerParser.Listener listener)
|
||||||
|
{
|
||||||
|
super(headerParser);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining() || state == State.PARAM)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case LENGTH:
|
||||||
|
{
|
||||||
|
length = getContentLength();
|
||||||
|
state = State.NAME_LENGTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NAME_LENGTH:
|
||||||
|
{
|
||||||
|
if (isLargeLength(buffer))
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 4)
|
||||||
|
{
|
||||||
|
nameLength = buffer.getInt() & 0x7F_FF;
|
||||||
|
state = State.VALUE_LENGTH;
|
||||||
|
length -= 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.NAME_LENGTH_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nameLength = buffer.get() & 0xFF;
|
||||||
|
state = State.VALUE_LENGTH;
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NAME_LENGTH_BYTES:
|
||||||
|
{
|
||||||
|
int quarterInt = buffer.get() & 0xFF;
|
||||||
|
nameLength = (nameLength << 8) + quarterInt;
|
||||||
|
--length;
|
||||||
|
if (++cursor == 4)
|
||||||
|
{
|
||||||
|
nameLength &= 0x7F_FF;
|
||||||
|
state = State.VALUE_LENGTH;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VALUE_LENGTH:
|
||||||
|
{
|
||||||
|
if (isLargeLength(buffer))
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= 4)
|
||||||
|
{
|
||||||
|
valueLength = buffer.getInt() & 0x7F_FF;
|
||||||
|
state = State.NAME;
|
||||||
|
length -= 4;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.VALUE_LENGTH_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
valueLength = buffer.get() & 0xFF;
|
||||||
|
state = State.NAME;
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VALUE_LENGTH_BYTES:
|
||||||
|
{
|
||||||
|
int quarterInt = buffer.get() & 0xFF;
|
||||||
|
valueLength = (valueLength << 8) + quarterInt;
|
||||||
|
--length;
|
||||||
|
if (++cursor == 4)
|
||||||
|
{
|
||||||
|
valueLength &= 0x7F_FF;
|
||||||
|
state = State.NAME;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NAME:
|
||||||
|
{
|
||||||
|
nameBytes = new byte[nameLength];
|
||||||
|
if (buffer.remaining() >= nameLength)
|
||||||
|
{
|
||||||
|
buffer.get(nameBytes);
|
||||||
|
state = State.VALUE;
|
||||||
|
length -= nameLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.NAME_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case NAME_BYTES:
|
||||||
|
{
|
||||||
|
nameBytes[cursor] = buffer.get();
|
||||||
|
--length;
|
||||||
|
if (++cursor == nameLength)
|
||||||
|
state = State.VALUE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VALUE:
|
||||||
|
{
|
||||||
|
valueBytes = new byte[valueLength];
|
||||||
|
if (buffer.remaining() >= valueLength)
|
||||||
|
{
|
||||||
|
buffer.get(valueBytes);
|
||||||
|
state = State.PARAM;
|
||||||
|
length -= valueLength;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
state = State.VALUE_BYTES;
|
||||||
|
cursor = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case VALUE_BYTES:
|
||||||
|
{
|
||||||
|
valueBytes[cursor] = buffer.get();
|
||||||
|
--length;
|
||||||
|
if (++cursor == valueLength)
|
||||||
|
state = State.PARAM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARAM:
|
||||||
|
{
|
||||||
|
Charset utf8 = Charset.forName("UTF-8");
|
||||||
|
onParam(new String(nameBytes, utf8), new String(valueBytes, utf8));
|
||||||
|
partialReset();
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noContent()
|
||||||
|
{
|
||||||
|
onParams();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onParam(String name, String value)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onHeader(getRequest(), new HttpField(name, value));
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onParams()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onHeaders(getRequest());
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isLargeLength(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
return (buffer.get(buffer.position()) & 0x80) == 0x80;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void partialReset()
|
||||||
|
{
|
||||||
|
state = State.NAME_LENGTH;
|
||||||
|
cursor = 0;
|
||||||
|
nameLength = 0;
|
||||||
|
valueLength = 0;
|
||||||
|
nameBytes = null;
|
||||||
|
valueBytes = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
partialReset();
|
||||||
|
state = State.LENGTH;
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
LENGTH, NAME_LENGTH, NAME_LENGTH_BYTES, VALUE_LENGTH, VALUE_LENGTH_BYTES, NAME, NAME_BYTES, VALUE, VALUE_BYTES, PARAM
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,131 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
|
||||||
|
public abstract class Parser
|
||||||
|
{
|
||||||
|
protected final HeaderParser headerParser = new HeaderParser();
|
||||||
|
private State state = State.HEADER;
|
||||||
|
private int padding;
|
||||||
|
|
||||||
|
public void parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case HEADER:
|
||||||
|
{
|
||||||
|
if (!headerParser.parse(buffer))
|
||||||
|
return;
|
||||||
|
state = State.CONTENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONTENT:
|
||||||
|
{
|
||||||
|
ContentParser contentParser = findContentParser(headerParser.getFrameType());
|
||||||
|
if (headerParser.getContentLength() == 0)
|
||||||
|
{
|
||||||
|
contentParser.noContent();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!contentParser.parse(buffer))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
padding = headerParser.getPaddingLength();
|
||||||
|
state = State.PADDING;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PADDING:
|
||||||
|
{
|
||||||
|
if (buffer.remaining() >= padding)
|
||||||
|
{
|
||||||
|
buffer.position(buffer.position() + padding);
|
||||||
|
reset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
padding -= buffer.remaining();
|
||||||
|
buffer.position(buffer.limit());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract ContentParser findContentParser(FCGI.FrameType frameType);
|
||||||
|
|
||||||
|
private void reset()
|
||||||
|
{
|
||||||
|
headerParser.reset();
|
||||||
|
state = State.HEADER;
|
||||||
|
padding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener
|
||||||
|
{
|
||||||
|
public void onHeader(int request, HttpField field);
|
||||||
|
|
||||||
|
public void onHeaders(int request);
|
||||||
|
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer);
|
||||||
|
|
||||||
|
public void onEnd(int request);
|
||||||
|
|
||||||
|
public static class Adapter implements Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
HEADER, CONTENT, PADDING
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,328 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpParser;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class ResponseContentParser extends StreamContentParser
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(ResponseContentParser.class);
|
||||||
|
|
||||||
|
private final Map<Integer, ResponseParser> parsers = new ConcurrentHashMap<>();
|
||||||
|
private final ClientParser.Listener listener;
|
||||||
|
|
||||||
|
public ResponseContentParser(HeaderParser headerParser, ClientParser.Listener listener)
|
||||||
|
{
|
||||||
|
super(headerParser, FCGI.StreamType.STD_OUT, listener);
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noContent()
|
||||||
|
{
|
||||||
|
// Does nothing, since for responses the end of content is signaled via a FCGI_END_REQUEST frame
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onContent(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
int request = getRequest();
|
||||||
|
ResponseParser parser = parsers.get(request);
|
||||||
|
if (parser == null)
|
||||||
|
{
|
||||||
|
parser = new ResponseParser(listener, request);
|
||||||
|
parsers.put(request, parser);
|
||||||
|
}
|
||||||
|
parser.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void end(int request)
|
||||||
|
{
|
||||||
|
super.end(request);
|
||||||
|
parsers.remove(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResponseParser implements HttpParser.ResponseHandler<ByteBuffer>
|
||||||
|
{
|
||||||
|
private final HttpFields fields = new HttpFields();
|
||||||
|
private ClientParser.Listener listener;
|
||||||
|
private final int request;
|
||||||
|
private final FCGIHttpParser httpParser;
|
||||||
|
private State state = State.HEADERS;
|
||||||
|
private boolean seenResponseCode;
|
||||||
|
|
||||||
|
private ResponseParser(ClientParser.Listener listener, int request)
|
||||||
|
{
|
||||||
|
this.listener = listener;
|
||||||
|
this.request = request;
|
||||||
|
this.httpParser = new FCGIHttpParser(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer);
|
||||||
|
|
||||||
|
int remaining = buffer.remaining();
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case HEADERS:
|
||||||
|
{
|
||||||
|
if (httpParser.parseHeaders(buffer))
|
||||||
|
state = State.CONTENT_MODE;
|
||||||
|
remaining = buffer.remaining();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONTENT_MODE:
|
||||||
|
{
|
||||||
|
// If we have no indication of the content, then
|
||||||
|
// the HTTP parser will assume there is no content
|
||||||
|
// and will not parse it even if it is provided,
|
||||||
|
// so we have to parse it raw ourselves here.
|
||||||
|
boolean rawContent = fields.size() == 0 ||
|
||||||
|
(fields.get(HttpHeader.CONTENT_LENGTH) == null &&
|
||||||
|
fields.get(HttpHeader.TRANSFER_ENCODING) == null);
|
||||||
|
state = rawContent ? State.RAW_CONTENT : State.HTTP_CONTENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case RAW_CONTENT:
|
||||||
|
{
|
||||||
|
notifyContent(buffer);
|
||||||
|
remaining = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case HTTP_CONTENT:
|
||||||
|
{
|
||||||
|
httpParser.parseContent(buffer);
|
||||||
|
remaining = buffer.remaining();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHeaderCacheSize()
|
||||||
|
{
|
||||||
|
// TODO: configure this
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean startResponse(HttpVersion version, int status, String reason)
|
||||||
|
{
|
||||||
|
// The HTTP request line does not exist in FCGI responses
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parsedHeader(HttpField httpField)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String name = httpField.getName();
|
||||||
|
if ("Status".equalsIgnoreCase(name))
|
||||||
|
{
|
||||||
|
if (!seenResponseCode)
|
||||||
|
{
|
||||||
|
seenResponseCode = true;
|
||||||
|
|
||||||
|
// Need to set the response status so the
|
||||||
|
// HttpParser can handle the content properly.
|
||||||
|
String[] parts = httpField.getValue().split(" ");
|
||||||
|
int code = Integer.parseInt(parts[0]);
|
||||||
|
httpParser.setResponseStatus(code);
|
||||||
|
String reason = parts.length > 1 ? parts[1] : HttpStatus.getMessage(code);
|
||||||
|
|
||||||
|
notifyBegin(code, reason);
|
||||||
|
notifyHeaders(fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (seenResponseCode)
|
||||||
|
notifyHeader(httpField);
|
||||||
|
else
|
||||||
|
fields.add(httpField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyBegin(int code, String reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onBegin(request, code, reason);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyHeader(HttpField httpField)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onHeader(request, httpField);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyHeaders(HttpFields fields)
|
||||||
|
{
|
||||||
|
if (fields != null)
|
||||||
|
{
|
||||||
|
for (HttpField field : fields)
|
||||||
|
notifyHeader(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyHeaders()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onHeaders(request);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean headerComplete()
|
||||||
|
{
|
||||||
|
if (!seenResponseCode)
|
||||||
|
{
|
||||||
|
// No Status header but we have other headers, assume 200 OK
|
||||||
|
notifyBegin(200, "OK");
|
||||||
|
notifyHeaders(fields);
|
||||||
|
}
|
||||||
|
notifyHeaders();
|
||||||
|
// Return from parsing so that we can parse the content
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean content(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
notifyContent(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyContent(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onContent(request, FCGI.StreamType.STD_OUT, buffer);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean messageComplete()
|
||||||
|
{
|
||||||
|
// Return from parsing so that we can parse the next headers or the raw content.
|
||||||
|
// No need to notify the listener because it will be done by FCGI_END_REQUEST.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void earlyEOF()
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void badMessage(int status, String reason)
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods overridden to make them visible here
|
||||||
|
private static class FCGIHttpParser extends HttpParser
|
||||||
|
{
|
||||||
|
private FCGIHttpParser(ResponseHandler<ByteBuffer> handler)
|
||||||
|
{
|
||||||
|
super(handler, 65 * 1024, true);
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
super.reset();
|
||||||
|
setState(State.HEADER);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean parseHeaders(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
return super.parseHeaders(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean parseContent(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
return super.parseContent(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setResponseStatus(int status)
|
||||||
|
{
|
||||||
|
super.setResponseStatus(status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
HEADERS, CONTENT_MODE, RAW_CONTENT, HTTP_CONTENT
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.util.EnumMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
|
||||||
|
public class ServerParser extends Parser
|
||||||
|
{
|
||||||
|
private final EnumMap<FCGI.FrameType, ContentParser> contentParsers = new EnumMap<>(FCGI.FrameType.class);
|
||||||
|
|
||||||
|
public ServerParser(Listener listener)
|
||||||
|
{
|
||||||
|
contentParsers.put(FCGI.FrameType.BEGIN_REQUEST, new BeginRequestContentParser(headerParser, listener));
|
||||||
|
contentParsers.put(FCGI.FrameType.PARAMS, new ParamsContentParser(headerParser, listener));
|
||||||
|
contentParsers.put(FCGI.FrameType.STDIN, new StreamContentParser(headerParser, FCGI.StreamType.STD_IN, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ContentParser findContentParser(FCGI.FrameType frameType)
|
||||||
|
{
|
||||||
|
return contentParsers.get(frameType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener extends Parser.Listener
|
||||||
|
{
|
||||||
|
public void onStart(int request, FCGI.Role role, int flags);
|
||||||
|
|
||||||
|
public static class Adapter extends Parser.Listener.Adapter implements Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onStart(int request, FCGI.Role role, int flags)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class StreamContentParser extends ContentParser
|
||||||
|
{
|
||||||
|
protected static final Logger logger = Log.getLogger(StreamContentParser.class);
|
||||||
|
|
||||||
|
private final FCGI.StreamType streamType;
|
||||||
|
private final Parser.Listener listener;
|
||||||
|
private State state = State.LENGTH;
|
||||||
|
private int contentLength;
|
||||||
|
|
||||||
|
public StreamContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener)
|
||||||
|
{
|
||||||
|
super(headerParser);
|
||||||
|
this.streamType = streamType;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case LENGTH:
|
||||||
|
{
|
||||||
|
contentLength = getContentLength();
|
||||||
|
state = State.CONTENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CONTENT:
|
||||||
|
{
|
||||||
|
int length = Math.min(contentLength, buffer.remaining());
|
||||||
|
int limit = buffer.limit();
|
||||||
|
buffer.limit(buffer.position() + length);
|
||||||
|
ByteBuffer slice = buffer.slice();
|
||||||
|
onContent(slice);
|
||||||
|
buffer.position(buffer.limit());
|
||||||
|
buffer.limit(limit);
|
||||||
|
contentLength -= length;
|
||||||
|
if (contentLength > 0)
|
||||||
|
break;
|
||||||
|
state = State.LENGTH;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void noContent()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onEnd(getRequest());
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onContent(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.onContent(getRequest(), streamType, buffer);
|
||||||
|
}
|
||||||
|
catch (Throwable x)
|
||||||
|
{
|
||||||
|
logger.debug("Exception while invoking listener " + listener, x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void end(int request)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
LENGTH, CONTENT
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.generator;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.parser.ServerParser;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ClientGeneratorTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testGenerateRequestHeaders() throws Exception
|
||||||
|
{
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
|
||||||
|
// Short name, short value
|
||||||
|
final String shortShortName = "REQUEST_METHOD";
|
||||||
|
final String shortShortValue = "GET";
|
||||||
|
fields.put(new HttpField(shortShortName, shortShortValue));
|
||||||
|
|
||||||
|
// Short name, long value
|
||||||
|
final String shortLongName = "REQUEST_URI";
|
||||||
|
// Be sure it's longer than 127 chars to test the large value
|
||||||
|
final String shortLongValue = "/api/0.6/map?bbox=-64.217736,-31.456810,-64.187736,-31.432322,filler=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||||
|
fields.put(new HttpField(shortLongName, shortLongValue));
|
||||||
|
|
||||||
|
// Long name, short value
|
||||||
|
// Be sure it's longer than 127 chars to test the large name
|
||||||
|
final String longShortName = "FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210";
|
||||||
|
final String longShortValue = "api.openstreetmap.org";
|
||||||
|
fields.put(new HttpField(longShortName, longShortValue));
|
||||||
|
|
||||||
|
// Long name, long value
|
||||||
|
char[] chars = new char[ClientGenerator.MAX_PARAM_LENGTH];
|
||||||
|
Arrays.fill(chars, 'z');
|
||||||
|
final String longLongName = new String(chars);
|
||||||
|
final String longLongValue = new String(chars);
|
||||||
|
fields.put(new HttpField(longLongName, longLongValue));
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ClientGenerator generator = new ClientGenerator(byteBufferPool);
|
||||||
|
final int id = 13;
|
||||||
|
Generator.Result result = generator.generateRequestHeaders(id, fields, null);
|
||||||
|
|
||||||
|
// Use the fundamental theorem of arithmetic to test the results.
|
||||||
|
// This way we know onHeader() has been called the right number of
|
||||||
|
// times with the right arguments, and so onHeaders().
|
||||||
|
final int[] primes = new int[]{2, 3, 5, 7, 11};
|
||||||
|
int value = 1;
|
||||||
|
for (int prime : primes)
|
||||||
|
value *= prime;
|
||||||
|
|
||||||
|
final AtomicInteger params = new AtomicInteger(1);
|
||||||
|
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
switch (field.getName())
|
||||||
|
{
|
||||||
|
case shortShortName:
|
||||||
|
Assert.assertEquals(shortShortValue, field.getValue());
|
||||||
|
params.set(params.get() * primes[0]);
|
||||||
|
break;
|
||||||
|
case shortLongName:
|
||||||
|
Assert.assertEquals(shortLongValue, field.getValue());
|
||||||
|
params.set(params.get() * primes[1]);
|
||||||
|
break;
|
||||||
|
case longShortName:
|
||||||
|
Assert.assertEquals(longShortValue, field.getValue());
|
||||||
|
params.set(params.get() * primes[2]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Assert.assertEquals(longLongName, field.getName());
|
||||||
|
Assert.assertEquals(longLongValue, field.getValue());
|
||||||
|
params.set(params.get() * primes[3]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
params.set(params.get() * primes[4]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(value, params.get());
|
||||||
|
|
||||||
|
// Parse again byte by byte
|
||||||
|
params.set(1);
|
||||||
|
for (ByteBuffer buffer : result.getByteBuffers())
|
||||||
|
{
|
||||||
|
buffer.flip();
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(value, params.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateSmallRequestContent() throws Exception
|
||||||
|
{
|
||||||
|
testGenerateRequestContent(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateLargeRequestContent() throws Exception
|
||||||
|
{
|
||||||
|
testGenerateRequestContent(128 * 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGenerateRequestContent(final int contentLength) throws Exception
|
||||||
|
{
|
||||||
|
ByteBuffer content = ByteBuffer.allocate(contentLength);
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ClientGenerator generator = new ClientGenerator(byteBufferPool);
|
||||||
|
final int id = 13;
|
||||||
|
Generator.Result result = generator.generateRequestContent(id, content, true, null);
|
||||||
|
|
||||||
|
final AtomicInteger totalLength = new AtomicInteger();
|
||||||
|
ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
totalLength.addAndGet(buffer.remaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
Assert.assertEquals(contentLength, totalLength.get());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse again one byte at a time
|
||||||
|
for (ByteBuffer buffer : result.getByteBuffers())
|
||||||
|
{
|
||||||
|
buffer.flip();
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.parser;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ClientParserTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testParseResponseHeaders() throws Exception
|
||||||
|
{
|
||||||
|
final int id = 13;
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
|
||||||
|
final int statusCode = 200;
|
||||||
|
final String statusMessage = "OK";
|
||||||
|
final String contentTypeName = "Content-Type";
|
||||||
|
final String contentTypeValue = "text/html;charset=utf-8";
|
||||||
|
fields.put(contentTypeName, contentTypeValue);
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
|
Generator.Result result = generator.generateResponseHeaders(id, statusCode, statusMessage, fields, null);
|
||||||
|
|
||||||
|
// Use the fundamental theorem of arithmetic to test the results.
|
||||||
|
// This way we know onHeader() has been called the right number of
|
||||||
|
// times with the right arguments, and so onHeaders().
|
||||||
|
final int[] primes = new int[]{2, 3, 5};
|
||||||
|
int value = 1;
|
||||||
|
for (int prime : primes)
|
||||||
|
value *= prime;
|
||||||
|
|
||||||
|
final AtomicInteger params = new AtomicInteger(1);
|
||||||
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBegin(int request, int code, String reason)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(statusCode, code);
|
||||||
|
Assert.assertEquals(statusMessage, reason);
|
||||||
|
params.set(params.get() * primes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
switch (field.getName())
|
||||||
|
{
|
||||||
|
case contentTypeName:
|
||||||
|
Assert.assertEquals(contentTypeValue, field.getValue());
|
||||||
|
params.set(params.get() * primes[1]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
params.set(params.get() * primes[2]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(value, params.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseNoResponseContent() throws Exception
|
||||||
|
{
|
||||||
|
final int id = 13;
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
fields.put("Content-Length", "0");
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
|
Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null);
|
||||||
|
Generator.Result result2 = generator.generateResponseContent(id, null, true, null);
|
||||||
|
|
||||||
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
verifier.addAndGet(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
verifier.addAndGet(3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result1.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
for (ByteBuffer buffer : result2.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(3, verifier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseSmallResponseContent() throws Exception
|
||||||
|
{
|
||||||
|
final int id = 13;
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
|
||||||
|
ByteBuffer content = ByteBuffer.wrap(new byte[1024]);
|
||||||
|
final int contentLength = content.remaining();
|
||||||
|
|
||||||
|
final int code = 200;
|
||||||
|
final String contentTypeName = "Content-Length";
|
||||||
|
final String contentTypeValue = String.valueOf(contentLength);
|
||||||
|
fields.put(contentTypeName, contentTypeValue);
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
||||||
|
|
||||||
|
final AtomicInteger verifier = new AtomicInteger();
|
||||||
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
Assert.assertEquals(contentLength, buffer.remaining());
|
||||||
|
verifier.addAndGet(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
verifier.addAndGet(3);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result1.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
for (ByteBuffer buffer : result2.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertEquals(5, verifier.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseLargeResponseContent() throws Exception
|
||||||
|
{
|
||||||
|
final int id = 13;
|
||||||
|
HttpFields fields = new HttpFields();
|
||||||
|
|
||||||
|
ByteBuffer content = ByteBuffer.wrap(new byte[128 * 1024]);
|
||||||
|
final int contentLength = content.remaining();
|
||||||
|
|
||||||
|
final int code = 200;
|
||||||
|
final String contentTypeName = "Content-Length";
|
||||||
|
final String contentTypeValue = String.valueOf(contentLength);
|
||||||
|
fields.put(contentTypeName, contentTypeValue);
|
||||||
|
|
||||||
|
ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||||
|
ServerGenerator generator = new ServerGenerator(byteBufferPool);
|
||||||
|
Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null);
|
||||||
|
Generator.Result result2 = generator.generateResponseContent(id, content, true, null);
|
||||||
|
|
||||||
|
final AtomicInteger totalLength = new AtomicInteger();
|
||||||
|
final AtomicBoolean verifier = new AtomicBoolean();
|
||||||
|
ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
totalLength.addAndGet(buffer.remaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(id, request);
|
||||||
|
Assert.assertEquals(contentLength, totalLength.get());
|
||||||
|
verifier.set(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (ByteBuffer buffer : result1.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
for (ByteBuffer buffer : result2.getByteBuffers())
|
||||||
|
{
|
||||||
|
parser.parse(buffer);
|
||||||
|
Assert.assertFalse(buffer.hasRemaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(verifier.get());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>fcgi-distribution</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>Jetty :: FastCGI :: Distribution</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<distribution-directory>${project.build.directory}/distribution</distribution-directory>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jars</id>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeGroupIds>org.eclipse.jetty.fcgi</includeGroupIds>
|
||||||
|
<excludeArtifactIds>fcgi-server</excludeArtifactIds>
|
||||||
|
<includeTypes>jar</includeTypes>
|
||||||
|
<outputDirectory>${distribution-directory}/lib/fcgi</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>assemble</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>assembly</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<finalName>jetty-fcgi-${project.version}</finalName>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>src/main/assembly/distribution.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
<tarLongFileMode>gnu</tarLongFileMode>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-proxy</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||||
|
|
||||||
|
<id>distribution</id>
|
||||||
|
|
||||||
|
<formats>
|
||||||
|
<format>tar.gz</format>
|
||||||
|
</formats>
|
||||||
|
|
||||||
|
<includeBaseDirectory>false</includeBaseDirectory>
|
||||||
|
|
||||||
|
<fileSets>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${project.basedir}/src/main/config/modules</directory>
|
||||||
|
<outputDirectory>/modules</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>*.mod</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
<fileSet>
|
||||||
|
<directory>${distribution-directory}</directory>
|
||||||
|
<outputDirectory>/</outputDirectory>
|
||||||
|
<includes>
|
||||||
|
<include>lib/**</include>
|
||||||
|
</includes>
|
||||||
|
</fileSet>
|
||||||
|
</fileSets>
|
||||||
|
|
||||||
|
</assembly>
|
|
@ -0,0 +1,16 @@
|
||||||
|
#
|
||||||
|
# FastCGI Module
|
||||||
|
#
|
||||||
|
|
||||||
|
[depend]
|
||||||
|
servlet
|
||||||
|
client
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
lib/jetty-security-${jetty.version}.jar
|
||||||
|
lib/jetty-proxy-${jetty.version}.jar
|
||||||
|
lib/fcgi/*.jar
|
||||||
|
|
||||||
|
[ini-template]
|
||||||
|
## For configuration of FastCGI contexts, see
|
||||||
|
## TODO: documentation url here
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
|
||||||
|
<Configure class="org.eclipse.jetty.servlet.ServletContextHandler">
|
||||||
|
|
||||||
|
<New id="root" class="java.lang.String">
|
||||||
|
<Arg>/var/www/wordpress-3.7.1</Arg>
|
||||||
|
</New>
|
||||||
|
|
||||||
|
<Set name="contextPath">/wp</Set>
|
||||||
|
<Set name="resourceBase"><Ref refid="root" /></Set>
|
||||||
|
<Set name="welcomeFiles">
|
||||||
|
<Array type="String"><Item>index.php</Item></Array>
|
||||||
|
</Set>
|
||||||
|
|
||||||
|
<Call name="addFilter">
|
||||||
|
<Arg>org.eclipse.jetty.fcgi.proxy.TryFilesFilter</Arg>
|
||||||
|
<Arg>/*</Arg>
|
||||||
|
<Arg>
|
||||||
|
<Call name="of" class="java.util.EnumSet">
|
||||||
|
<Arg><Get name="REQUEST" class="javax.servlet.DispatcherType" /></Arg>
|
||||||
|
</Call>
|
||||||
|
</Arg>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>files</Arg>
|
||||||
|
<Arg>$path /index.php?p=$path</Arg>
|
||||||
|
</Call>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
<Call name="addServlet">
|
||||||
|
<Arg>
|
||||||
|
<New class="org.eclipse.jetty.servlet.ServletHolder">
|
||||||
|
<Arg>default</Arg>
|
||||||
|
<Arg>
|
||||||
|
<Call name="forName" class="java.lang.Class">
|
||||||
|
<Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg>
|
||||||
|
</Call>
|
||||||
|
</Arg>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>dirAllowed</Arg>
|
||||||
|
<Arg>false</Arg>
|
||||||
|
</Call>
|
||||||
|
</New>
|
||||||
|
</Arg>
|
||||||
|
<Arg>/</Arg>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
<Call name="addServlet">
|
||||||
|
<Arg>org.eclipse.jetty.fcgi.proxy.FastCGIProxyServlet</Arg>
|
||||||
|
<Arg>*.php</Arg>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>proxyTo</Arg>
|
||||||
|
<Arg>http://localhost:9000</Arg>
|
||||||
|
</Call>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>prefix</Arg>
|
||||||
|
<Arg>/</Arg>
|
||||||
|
</Call>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>scriptRoot</Arg>
|
||||||
|
<Arg><Ref refid="root" /></Arg>
|
||||||
|
</Call>
|
||||||
|
<Call name="setInitParameter">
|
||||||
|
<Arg>scriptPattern</Arg>
|
||||||
|
<Arg>(.+?\\.php)</Arg>
|
||||||
|
</Call>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
</Configure>
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>fcgi-http-client-transport</artifactId>
|
||||||
|
<name>Jetty :: FastCGI :: HTTP Client Transport</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-client</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-server</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,167 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.io.IdleTimeout;
|
||||||
|
|
||||||
|
public class HttpChannelOverFCGI extends HttpChannel
|
||||||
|
{
|
||||||
|
private final HttpConnectionOverFCGI connection;
|
||||||
|
private final Flusher flusher;
|
||||||
|
private final int request;
|
||||||
|
private final HttpSenderOverFCGI sender;
|
||||||
|
private final HttpReceiverOverFCGI receiver;
|
||||||
|
private final FCGIIdleTimeout idle;
|
||||||
|
private HttpVersion version;
|
||||||
|
|
||||||
|
public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout)
|
||||||
|
{
|
||||||
|
super(connection.getHttpDestination());
|
||||||
|
this.connection = connection;
|
||||||
|
this.flusher = flusher;
|
||||||
|
this.request = request;
|
||||||
|
this.sender = new HttpSenderOverFCGI(this);
|
||||||
|
this.receiver = new HttpReceiverOverFCGI(this);
|
||||||
|
this.idle = new FCGIIdleTimeout(connection, idleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getRequest()
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send()
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
{
|
||||||
|
version = exchange.getRequest().getVersion();
|
||||||
|
sender.send(exchange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void proceed(HttpExchange exchange, Throwable failure)
|
||||||
|
{
|
||||||
|
sender.proceed(exchange, failure);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean abort(Throwable cause)
|
||||||
|
{
|
||||||
|
sender.abort(cause);
|
||||||
|
return receiver.abort(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void responseBegin(int code, String reason)
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
{
|
||||||
|
exchange.getResponse().version(version).status(code).reason(reason);
|
||||||
|
receiver.responseBegin(exchange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void responseHeader(HttpField field)
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
receiver.responseHeader(exchange, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void responseHeaders()
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
receiver.responseHeaders(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void content(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
receiver.responseContent(exchange, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void responseSuccess()
|
||||||
|
{
|
||||||
|
HttpExchange exchange = getHttpExchange();
|
||||||
|
if (exchange != null)
|
||||||
|
receiver.responseSuccess(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exchangeTerminated(Result result)
|
||||||
|
{
|
||||||
|
super.exchangeTerminated(result);
|
||||||
|
idle.onClose();
|
||||||
|
boolean close = result.isFailed();
|
||||||
|
HttpFields responseHeaders = result.getResponse().getHeaders();
|
||||||
|
close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
|
||||||
|
if (close)
|
||||||
|
connection.close();
|
||||||
|
else
|
||||||
|
connection.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void flush(Generator.Result... results)
|
||||||
|
{
|
||||||
|
flusher.flush(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FCGIIdleTimeout extends IdleTimeout
|
||||||
|
{
|
||||||
|
private final HttpConnectionOverFCGI connection;
|
||||||
|
|
||||||
|
public FCGIIdleTimeout(HttpConnectionOverFCGI connection, long idleTimeout)
|
||||||
|
{
|
||||||
|
super(connection.getHttpDestination().getHttpClient().getScheduler());
|
||||||
|
this.connection = connection;
|
||||||
|
setIdleTimeout(idleTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onIdleExpired(TimeoutException timeout)
|
||||||
|
{
|
||||||
|
LOG.debug("Idle timeout for request {}", request);
|
||||||
|
abort(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen()
|
||||||
|
{
|
||||||
|
return connection.getEndPoint().isOpen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.Promise;
|
||||||
|
|
||||||
|
public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport
|
||||||
|
{
|
||||||
|
private final boolean multiplexed;
|
||||||
|
private final String scriptRoot;
|
||||||
|
|
||||||
|
public HttpClientTransportOverFCGI(String scriptRoot)
|
||||||
|
{
|
||||||
|
this(Math.max(1, Runtime.getRuntime().availableProcessors() / 2), false, scriptRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpClientTransportOverFCGI(int selectors, boolean multiplexed, String scriptRoot)
|
||||||
|
{
|
||||||
|
super(selectors);
|
||||||
|
this.multiplexed = multiplexed;
|
||||||
|
this.scriptRoot = scriptRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isMultiplexed()
|
||||||
|
{
|
||||||
|
return multiplexed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScriptRoot()
|
||||||
|
{
|
||||||
|
return scriptRoot;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpDestination newHttpDestination(Origin origin)
|
||||||
|
{
|
||||||
|
return isMultiplexed() ? new MultiplexHttpDestinationOverFCGI(getHttpClient(), origin)
|
||||||
|
: new HttpDestinationOverFCGI(getHttpClient(), origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> context) throws IOException
|
||||||
|
{
|
||||||
|
HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY);
|
||||||
|
HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination);
|
||||||
|
LOG.debug("Created {}", connection);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Promise<Connection> promise = (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||||
|
promise.succeeded(connection);
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void customize(Request request, HttpFields fastCGIHeaders)
|
||||||
|
{
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.DOCUMENT_ROOT, getScriptRoot());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,324 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpConnection;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.PoolingHttpDestination;
|
||||||
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
|
import org.eclipse.jetty.fcgi.parser.ClientParser;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class HttpConnectionOverFCGI extends AbstractConnection implements Connection
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class);
|
||||||
|
|
||||||
|
private final LinkedList<Integer> requests = new LinkedList<>();
|
||||||
|
private final Map<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
|
||||||
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
|
private final Flusher flusher;
|
||||||
|
private final HttpDestination destination;
|
||||||
|
private final Delegate delegate;
|
||||||
|
private final ClientParser parser;
|
||||||
|
|
||||||
|
public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination)
|
||||||
|
{
|
||||||
|
super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO());
|
||||||
|
this.flusher = new Flusher(endPoint);
|
||||||
|
this.destination = destination;
|
||||||
|
this.delegate = new Delegate(destination);
|
||||||
|
this.parser = new ClientParser(new ResponseListener());
|
||||||
|
requests.addLast(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HttpDestination getHttpDestination()
|
||||||
|
{
|
||||||
|
return destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(Request request, Response.CompleteListener listener)
|
||||||
|
{
|
||||||
|
delegate.send(request, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void send(HttpExchange exchange)
|
||||||
|
{
|
||||||
|
delegate.send(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
EndPoint endPoint = getEndPoint();
|
||||||
|
HttpClient client = destination.getHttpClient();
|
||||||
|
ByteBufferPool bufferPool = client.getByteBufferPool();
|
||||||
|
ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int read = endPoint.fill(buffer);
|
||||||
|
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
|
||||||
|
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||||
|
if (read > 0)
|
||||||
|
{
|
||||||
|
parse(buffer);
|
||||||
|
}
|
||||||
|
else if (read == 0)
|
||||||
|
{
|
||||||
|
fillInterested();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
LOG.debug(x);
|
||||||
|
// TODO: fail and close ?
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bufferPool.release(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
parser.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown()
|
||||||
|
{
|
||||||
|
// First close then abort, to be sure that the
|
||||||
|
// connection cannot be reused from an onFailure()
|
||||||
|
// handler or by blocking code waiting for completion.
|
||||||
|
close();
|
||||||
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
channel.abort(new EOFException());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onReadTimeout()
|
||||||
|
{
|
||||||
|
for (HttpChannelOverFCGI channel : channels.values())
|
||||||
|
channel.abort(new TimeoutException());
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release()
|
||||||
|
{
|
||||||
|
if (destination instanceof PoolingHttpDestination)
|
||||||
|
{
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
PoolingHttpDestination<HttpConnectionOverFCGI> fcgiDestination =
|
||||||
|
(PoolingHttpDestination<HttpConnectionOverFCGI>)destination;
|
||||||
|
fcgiDestination.release(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
if (closed.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
getHttpDestination().close(this);
|
||||||
|
getEndPoint().shutdownOutput();
|
||||||
|
LOG.debug("{} oshut", this);
|
||||||
|
getEndPoint().close();
|
||||||
|
LOG.debug("{} closed", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int acquireRequest()
|
||||||
|
{
|
||||||
|
synchronized (requests)
|
||||||
|
{
|
||||||
|
int last = requests.getLast();
|
||||||
|
int request = last + 1;
|
||||||
|
requests.addLast(request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseRequest(int request)
|
||||||
|
{
|
||||||
|
synchronized (requests)
|
||||||
|
{
|
||||||
|
requests.removeFirstOccurrence(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("%s@%h(l:%s <-> r:%s)",
|
||||||
|
getClass().getSimpleName(),
|
||||||
|
this,
|
||||||
|
getEndPoint().getLocalAddress(),
|
||||||
|
getEndPoint().getRemoteAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Delegate extends HttpConnection
|
||||||
|
{
|
||||||
|
private Delegate(HttpDestination destination)
|
||||||
|
{
|
||||||
|
super(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void send(HttpExchange exchange)
|
||||||
|
{
|
||||||
|
Request request = exchange.getRequest();
|
||||||
|
normalizeRequest(request);
|
||||||
|
|
||||||
|
// FCGI may be multiplexed, so create one channel for each request.
|
||||||
|
int id = acquireRequest();
|
||||||
|
HttpChannelOverFCGI channel = new HttpChannelOverFCGI(HttpConnectionOverFCGI.this, flusher, id, request.getIdleTimeout());
|
||||||
|
channels.put(id, channel);
|
||||||
|
channel.associate(exchange);
|
||||||
|
channel.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close()
|
||||||
|
{
|
||||||
|
HttpConnectionOverFCGI.this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return HttpConnectionOverFCGI.this.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ResponseListener implements ClientParser.Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onBegin(int request, int code, String reason)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
channel.responseBegin(code, reason);
|
||||||
|
else
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
channel.responseHeader(field);
|
||||||
|
else
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
channel.responseHeaders();
|
||||||
|
else
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
switch (stream)
|
||||||
|
{
|
||||||
|
case STD_OUT:
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (channel != null)
|
||||||
|
channel.content(buffer);
|
||||||
|
else
|
||||||
|
noChannel(request);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STD_ERR:
|
||||||
|
{
|
||||||
|
LOG.info(BufferUtil.toUTF8String(buffer));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.remove(request);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
channel.responseSuccess();
|
||||||
|
releaseRequest(request);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
noChannel(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void noChannel(int request)
|
||||||
|
{
|
||||||
|
// TODO: what here ?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.client.PoolingHttpDestination;
|
||||||
|
|
||||||
|
public class HttpDestinationOverFCGI extends PoolingHttpDestination<HttpConnectionOverFCGI>
|
||||||
|
{
|
||||||
|
public HttpDestinationOverFCGI(HttpClient client, Origin origin)
|
||||||
|
{
|
||||||
|
super(client, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange)
|
||||||
|
{
|
||||||
|
connection.send(exchange);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.HttpReceiver;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
|
||||||
|
public class HttpReceiverOverFCGI extends HttpReceiver
|
||||||
|
{
|
||||||
|
public HttpReceiverOverFCGI(HttpChannel channel)
|
||||||
|
{
|
||||||
|
super(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseBegin(HttpExchange exchange)
|
||||||
|
{
|
||||||
|
return super.responseBegin(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseHeader(HttpExchange exchange, HttpField field)
|
||||||
|
{
|
||||||
|
return super.responseHeader(exchange, field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseHeaders(HttpExchange exchange)
|
||||||
|
{
|
||||||
|
return super.responseHeaders(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
return super.responseContent(exchange, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseSuccess(HttpExchange exchange)
|
||||||
|
{
|
||||||
|
return super.responseSuccess(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean responseFailure(Throwable failure)
|
||||||
|
{
|
||||||
|
return super.responseFailure(failure);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpChannel;
|
||||||
|
import org.eclipse.jetty.client.HttpContent;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.HttpSender;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.ClientGenerator;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpHeader;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
import org.eclipse.jetty.util.Jetty;
|
||||||
|
|
||||||
|
public class HttpSenderOverFCGI extends HttpSender
|
||||||
|
{
|
||||||
|
private final ClientGenerator generator;
|
||||||
|
|
||||||
|
public HttpSenderOverFCGI(HttpChannel channel)
|
||||||
|
{
|
||||||
|
super(channel);
|
||||||
|
this.generator = new ClientGenerator(channel.getHttpDestination().getHttpClient().getByteBufferPool());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpChannelOverFCGI getHttpChannel()
|
||||||
|
{
|
||||||
|
return (HttpChannelOverFCGI)super.getHttpChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
|
||||||
|
{
|
||||||
|
Request request = exchange.getRequest();
|
||||||
|
// Copy the request headers to be able to convert them properly
|
||||||
|
HttpFields headers = new HttpFields();
|
||||||
|
for (HttpField field : request.getHeaders())
|
||||||
|
headers.put(field);
|
||||||
|
HttpFields fcgiHeaders = new HttpFields();
|
||||||
|
|
||||||
|
// FastCGI headers based on the URI
|
||||||
|
URI uri = request.getURI();
|
||||||
|
String path = uri.getRawPath();
|
||||||
|
fcgiHeaders.put(FCGI.Headers.DOCUMENT_URI, path);
|
||||||
|
String query = uri.getRawQuery();
|
||||||
|
fcgiHeaders.put(FCGI.Headers.QUERY_STRING, query == null ? "" : query);
|
||||||
|
|
||||||
|
// FastCGI headers based on HTTP headers
|
||||||
|
HttpField httpField = headers.remove(HttpHeader.AUTHORIZATION);
|
||||||
|
if (httpField != null)
|
||||||
|
fcgiHeaders.put(FCGI.Headers.AUTH_TYPE, httpField.getValue());
|
||||||
|
httpField = headers.remove(HttpHeader.CONTENT_LENGTH);
|
||||||
|
fcgiHeaders.put(FCGI.Headers.CONTENT_LENGTH, httpField == null ? "" : httpField.getValue());
|
||||||
|
httpField = headers.remove(HttpHeader.CONTENT_TYPE);
|
||||||
|
fcgiHeaders.put(FCGI.Headers.CONTENT_TYPE, httpField == null ? "" : httpField.getValue());
|
||||||
|
|
||||||
|
// FastCGI headers that are not based on HTTP headers nor URI
|
||||||
|
fcgiHeaders.put(FCGI.Headers.REQUEST_METHOD, request.getMethod());
|
||||||
|
fcgiHeaders.put(FCGI.Headers.SERVER_PROTOCOL, request.getVersion().asString());
|
||||||
|
fcgiHeaders.put(FCGI.Headers.GATEWAY_INTERFACE, "CGI/1.1");
|
||||||
|
fcgiHeaders.put(FCGI.Headers.SERVER_SOFTWARE, "Jetty/" + Jetty.VERSION);
|
||||||
|
|
||||||
|
// Translate remaining HTTP header into the HTTP_* format
|
||||||
|
for (HttpField field : headers)
|
||||||
|
{
|
||||||
|
String name = field.getName();
|
||||||
|
String fcgiName = "HTTP_" + name.replaceAll("-", "_").toUpperCase(Locale.ENGLISH);
|
||||||
|
fcgiHeaders.add(fcgiName, field.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give a chance to the transport implementation to customize the FastCGI headers
|
||||||
|
HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)getHttpChannel().getHttpDestination().getHttpClient().getTransport();
|
||||||
|
transport.customize(request, fcgiHeaders);
|
||||||
|
|
||||||
|
int id = getHttpChannel().getRequest();
|
||||||
|
boolean hasContent = content.hasContent();
|
||||||
|
Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders,
|
||||||
|
hasContent ? callback : new Callback.Adapter());
|
||||||
|
if (hasContent)
|
||||||
|
{
|
||||||
|
getHttpChannel().flush(headersResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Generator.Result noContentResult = generator.generateRequestContent(id, BufferUtil.EMPTY_BUFFER, true, callback);
|
||||||
|
getHttpChannel().flush(headersResult, noContentResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
|
||||||
|
{
|
||||||
|
if (content.isConsumed())
|
||||||
|
{
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int request = getHttpChannel().getRequest();
|
||||||
|
Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback);
|
||||||
|
getHttpChannel().flush(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.MultiplexHttpDestination;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
|
||||||
|
public class MultiplexHttpDestinationOverFCGI extends MultiplexHttpDestination<HttpConnectionOverFCGI>
|
||||||
|
{
|
||||||
|
public MultiplexHttpDestinationOverFCGI(HttpClient client, Origin origin)
|
||||||
|
{
|
||||||
|
super(client, origin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void send(HttpConnectionOverFCGI connection, HttpExchange exchange)
|
||||||
|
{
|
||||||
|
connection.send(exchange);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.ConnectionPool;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
|
import org.eclipse.jetty.client.LeakTrackingConnectionPool;
|
||||||
|
import org.eclipse.jetty.client.Origin;
|
||||||
|
import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.io.ArrayByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.LeakTrackingByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
import org.eclipse.jetty.util.LeakDetector;
|
||||||
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Rule;
|
||||||
|
|
||||||
|
public abstract class AbstractHttpClientServerTest
|
||||||
|
{
|
||||||
|
@Rule
|
||||||
|
public final TestTracker tracker = new TestTracker();
|
||||||
|
private final AtomicLong leaks = new AtomicLong();
|
||||||
|
protected Server server;
|
||||||
|
protected ServerConnector connector;
|
||||||
|
protected HttpClient client;
|
||||||
|
protected String scheme = HttpScheme.HTTP.asString();
|
||||||
|
|
||||||
|
public void start(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
|
||||||
|
ServerFCGIConnectionFactory fcgiConnectionFactory = new ServerFCGIConnectionFactory(new HttpConfiguration());
|
||||||
|
connector = new ServerConnector(server, null, null,
|
||||||
|
new LeakTrackingByteBufferPool(new ArrayByteBufferPool())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void leaked(LeakDetector.LeakInfo leakInfo)
|
||||||
|
{
|
||||||
|
leaks.incrementAndGet();
|
||||||
|
}
|
||||||
|
}, 1, Math.max(1, Runtime.getRuntime().availableProcessors() / 2), fcgiConnectionFactory);
|
||||||
|
// connector.setPort(9000);
|
||||||
|
|
||||||
|
server.addConnector(connector);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
QueuedThreadPool executor = new QueuedThreadPool();
|
||||||
|
executor.setName(executor.getName() + "-client");
|
||||||
|
|
||||||
|
client = new HttpClient(new HttpClientTransportOverFCGI(1, false, "")
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public HttpDestination newHttpDestination(Origin origin)
|
||||||
|
{
|
||||||
|
return new HttpDestinationOverFCGI(client, origin)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected ConnectionPool newConnectionPool(HttpClient client)
|
||||||
|
{
|
||||||
|
return new LeakTrackingConnectionPool(this, client.getMaxConnectionsPerDestination(), this)
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void leaked(LeakDetector.LeakInfo leakInfo)
|
||||||
|
{
|
||||||
|
leaks.incrementAndGet();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, null);
|
||||||
|
client.setExecutor(executor);
|
||||||
|
client.setByteBufferPool(new LeakTrackingByteBufferPool(new MappedByteBufferPool())
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void leaked(LeakDetector.LeakInfo leakInfo)
|
||||||
|
{
|
||||||
|
leaks.incrementAndGet();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void dispose() throws Exception
|
||||||
|
{
|
||||||
|
System.gc();
|
||||||
|
Assert.assertEquals(0, leaks.get());
|
||||||
|
|
||||||
|
if (client != null)
|
||||||
|
client.stop();
|
||||||
|
if (server != null)
|
||||||
|
server.stop();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Request;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
|
public class EmptyServerHandler extends AbstractHandler
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ExternalFastCGIServerTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
@Ignore("Relies on an external server")
|
||||||
|
public void testExternalFastCGIServer() throws Exception
|
||||||
|
{
|
||||||
|
// Assume a FastCGI server is listening on localhost:9000
|
||||||
|
|
||||||
|
HttpClient client = new HttpClient(new HttpClientTransportOverFCGI("/var/www/php-fcgi"), null);
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", 9000)
|
||||||
|
.path("/index.php")
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
|
||||||
|
Path responseFile = Paths.get(System.getProperty("java.io.tmpdir"), "fcgi_response.html");
|
||||||
|
Files.write(responseFile, response.getContent(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,516 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.client.http;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.zip.GZIPOutputStream;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
import org.eclipse.jetty.toolchain.test.IO;
|
||||||
|
import org.eclipse.jetty.toolchain.test.annotation.Slow;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class HttpClientTest extends AbstractHttpClientServerTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testGETResponseWithoutContent() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
for (int i = 0; i < 2; ++i)
|
||||||
|
{
|
||||||
|
Response response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGETResponseWithContent() throws Exception
|
||||||
|
{
|
||||||
|
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
response.getOutputStream().write(data);
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int maxConnections = 256;
|
||||||
|
client.setMaxConnectionsPerDestination(maxConnections);
|
||||||
|
|
||||||
|
for (int i = 0; i < maxConnections + 1; ++i)
|
||||||
|
{
|
||||||
|
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
byte[] content = response.getContent();
|
||||||
|
Assert.assertArrayEquals(data, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGETWithParametersResponseWithContent() throws Exception
|
||||||
|
{
|
||||||
|
final String paramName1 = "a";
|
||||||
|
final String paramName2 = "b";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
String paramValue1 = request.getParameter(paramName1);
|
||||||
|
output.write(paramValue1.getBytes("UTF-8"));
|
||||||
|
String paramValue2 = request.getParameter(paramName2);
|
||||||
|
Assert.assertEquals("", paramValue2);
|
||||||
|
output.write("empty".getBytes("UTF-8"));
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String value1 = "\u20AC";
|
||||||
|
String paramValue1 = URLEncoder.encode(value1, "UTF-8");
|
||||||
|
String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
|
||||||
|
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
String content = new String(response.getContent(), "UTF-8");
|
||||||
|
Assert.assertEquals(value1 + "empty", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGETWithParametersMultiValuedResponseWithContent() throws Exception
|
||||||
|
{
|
||||||
|
final String paramName1 = "a";
|
||||||
|
final String paramName2 = "b";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
String[] paramValues1 = request.getParameterValues(paramName1);
|
||||||
|
for (String paramValue : paramValues1)
|
||||||
|
output.write(paramValue.getBytes("UTF-8"));
|
||||||
|
String paramValue2 = request.getParameter(paramName2);
|
||||||
|
output.write(paramValue2.getBytes("UTF-8"));
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String value11 = "\u20AC";
|
||||||
|
String value12 = "\u20AA";
|
||||||
|
String value2 = "&";
|
||||||
|
String paramValue11 = URLEncoder.encode(value11, "UTF-8");
|
||||||
|
String paramValue12 = URLEncoder.encode(value12, "UTF-8");
|
||||||
|
String paramValue2 = URLEncoder.encode(value2, "UTF-8");
|
||||||
|
String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
|
||||||
|
ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
String content = new String(response.getContent(), "UTF-8");
|
||||||
|
Assert.assertEquals(value11 + value12 + value2, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPOSTWithParameters() throws Exception
|
||||||
|
{
|
||||||
|
final String paramName = "a";
|
||||||
|
final String paramValue = "\u20AC";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
String value = request.getParameter(paramName);
|
||||||
|
if (paramValue.equals(value))
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
response.getOutputStream().print(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
||||||
|
.param(paramName, paramValue)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPOSTWithQueryString() throws Exception
|
||||||
|
{
|
||||||
|
final String paramName = "a";
|
||||||
|
final String paramValue = "\u20AC";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
String value = request.getParameter(paramName);
|
||||||
|
if (paramValue.equals(value))
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
response.getOutputStream().print(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String uri = scheme + "://localhost:" + connector.getLocalPort() +
|
||||||
|
"/?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
|
||||||
|
ContentResponse response = client.POST(uri)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPUTWithParameters() throws Exception
|
||||||
|
{
|
||||||
|
final String paramName = "a";
|
||||||
|
final String paramValue = "\u20AC";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
String value = request.getParameter(paramName);
|
||||||
|
if (paramValue.equals(value))
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
response.getOutputStream().print(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort() + "/path?" + paramName + "=" + paramValue);
|
||||||
|
ContentResponse response = client.newRequest(uri)
|
||||||
|
.method(HttpMethod.PUT)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPOSTWithParametersWithContent() throws Exception
|
||||||
|
{
|
||||||
|
final byte[] content = {0, 1, 2, 3};
|
||||||
|
final String paramName = "a";
|
||||||
|
final String paramValue = "\u20AC";
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
String value = request.getParameter(paramName);
|
||||||
|
if (paramValue.equals(value))
|
||||||
|
{
|
||||||
|
response.setCharacterEncoding("UTF-8");
|
||||||
|
response.setContentType("application/octet-stream");
|
||||||
|
IO.copy(request.getInputStream(), response.getOutputStream());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int i = 0; i < 256; ++i)
|
||||||
|
{
|
||||||
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
|
||||||
|
.param(paramName, paramValue)
|
||||||
|
.content(new BytesContentProvider(content))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertArrayEquals(content, response.getContent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPOSTWithContentNotifiesRequestContentListener() throws Exception
|
||||||
|
{
|
||||||
|
final byte[] content = {0, 1, 2, 3};
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
||||||
|
.onRequestContent(new Request.ContentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(Request request, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[buffer.remaining()];
|
||||||
|
buffer.get(bytes);
|
||||||
|
if (!Arrays.equals(content, bytes))
|
||||||
|
request.abort(new Exception());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.content(new BytesContentProvider(content))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPOSTWithContentTracksProgress() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
final AtomicInteger progress = new AtomicInteger();
|
||||||
|
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort())
|
||||||
|
.onRequestContent(new Request.ContentListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onContent(Request request, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
byte[] bytes = new byte[buffer.remaining()];
|
||||||
|
Assert.assertEquals(1, bytes.length);
|
||||||
|
buffer.get(bytes);
|
||||||
|
Assert.assertEquals(bytes[0], progress.getAndIncrement());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(5, progress.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGZIPContentEncoding() throws Exception
|
||||||
|
{
|
||||||
|
final byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.setHeader("Content-Encoding", "gzip");
|
||||||
|
GZIPOutputStream gzipOutput = new GZIPOutputStream(response.getOutputStream());
|
||||||
|
gzipOutput.write(data);
|
||||||
|
gzipOutput.finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertArrayEquals(data, response.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Slow
|
||||||
|
@Test
|
||||||
|
public void testRequestIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
final long idleTimeout = 1000;
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||||
|
}
|
||||||
|
catch (InterruptedException x)
|
||||||
|
{
|
||||||
|
throw new ServletException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final String host = "localhost";
|
||||||
|
final int port = connector.getLocalPort();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest(host, port)
|
||||||
|
.scheme(scheme)
|
||||||
|
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException expected)
|
||||||
|
{
|
||||||
|
Assert.assertTrue(expected.getCause() instanceof TimeoutException);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make another request without specifying the idle timeout, should not fail
|
||||||
|
ContentResponse response = client.newRequest(host, port)
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConnectionIdleTimeout() throws Exception
|
||||||
|
{
|
||||||
|
final long idleTimeout = 1000;
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(2 * idleTimeout);
|
||||||
|
}
|
||||||
|
catch (InterruptedException x)
|
||||||
|
{
|
||||||
|
throw new ServletException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connector.setIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
Assert.assertTrue(x.getCause() instanceof EOFException);
|
||||||
|
}
|
||||||
|
|
||||||
|
connector.setIdleTimeout(5 * idleTimeout);
|
||||||
|
|
||||||
|
// Make another request to be sure the connection is recreated
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.idleTimeout(4 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.timeout(3 * idleTimeout, TimeUnit.MILLISECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendToIPv6Address() throws Exception
|
||||||
|
{
|
||||||
|
start(new EmptyServerHandler());
|
||||||
|
|
||||||
|
ContentResponse response = client.newRequest("[::1]", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHEADWithResponseContentLength() throws Exception
|
||||||
|
{
|
||||||
|
final int length = 1024;
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.getOutputStream().write(new byte[length]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// HEAD requests receive a Content-Length header, but do not
|
||||||
|
// receive the content so they must handle this case properly
|
||||||
|
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.method(HttpMethod.HEAD)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(0, response.getContent().length);
|
||||||
|
|
||||||
|
// Perform a normal GET request to be sure the content is now read
|
||||||
|
response = client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.scheme(scheme)
|
||||||
|
.timeout(5, TimeUnit.SECONDS)
|
||||||
|
.send();
|
||||||
|
|
||||||
|
Assert.assertNotNull(response);
|
||||||
|
Assert.assertEquals(200, response.getStatus());
|
||||||
|
Assert.assertEquals(length, response.getContent().length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.fcgi.LEVEL=DEBUG
|
|
@ -0,0 +1,53 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>fcgi-proxy</artifactId>
|
||||||
|
<name>Jetty :: FastCGI :: Proxy</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-http-client-transport</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-proxy</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.spdy</groupId>
|
||||||
|
<artifactId>spdy-http-server</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,157 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.proxy;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
import org.eclipse.jetty.http.HttpScheme;
|
||||||
|
import org.eclipse.jetty.proxy.ProxyServlet;
|
||||||
|
|
||||||
|
public class FastCGIProxyServlet extends ProxyServlet.Transparent
|
||||||
|
{
|
||||||
|
public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot";
|
||||||
|
public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern";
|
||||||
|
private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr";
|
||||||
|
private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort";
|
||||||
|
private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName";
|
||||||
|
private static final String SERVER_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverAddr";
|
||||||
|
private static final String SERVER_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverPort";
|
||||||
|
private static final String SCHEME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".scheme";
|
||||||
|
private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI";
|
||||||
|
|
||||||
|
private Pattern scriptPattern;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init() throws ServletException
|
||||||
|
{
|
||||||
|
super.init();
|
||||||
|
|
||||||
|
String value = getInitParameter(SCRIPT_PATTERN_INIT_PARAM);
|
||||||
|
if (value == null)
|
||||||
|
value = "(.+?\\.php)";
|
||||||
|
scriptPattern = Pattern.compile(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected HttpClient newHttpClient()
|
||||||
|
{
|
||||||
|
ServletConfig config = getServletConfig();
|
||||||
|
String scriptRoot = config.getInitParameter(SCRIPT_ROOT_INIT_PARAM);
|
||||||
|
if (scriptRoot == null)
|
||||||
|
throw new IllegalArgumentException("Mandatory parameter '" + SCRIPT_ROOT_INIT_PARAM + "' not configured");
|
||||||
|
return new HttpClient(new ProxyHttpClientTransportOverFCGI(scriptRoot), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void customizeProxyRequest(Request proxyRequest, HttpServletRequest request)
|
||||||
|
{
|
||||||
|
proxyRequest.attribute(REMOTE_ADDR_ATTRIBUTE, request.getRemoteAddr());
|
||||||
|
proxyRequest.attribute(REMOTE_PORT_ATTRIBUTE, String.valueOf(request.getRemotePort()));
|
||||||
|
proxyRequest.attribute(SERVER_NAME_ATTRIBUTE, request.getServerName());
|
||||||
|
proxyRequest.attribute(SERVER_ADDR_ATTRIBUTE, request.getLocalAddr());
|
||||||
|
proxyRequest.attribute(SERVER_PORT_ATTRIBUTE, String.valueOf(request.getLocalPort()));
|
||||||
|
|
||||||
|
proxyRequest.attribute(SCHEME_ATTRIBUTE, request.getScheme());
|
||||||
|
|
||||||
|
// If we are forwarded or included, retain the original request URI.
|
||||||
|
String originalPath = (String)request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI);
|
||||||
|
String originalQuery = (String)request.getAttribute(RequestDispatcher.FORWARD_QUERY_STRING);
|
||||||
|
if (originalPath == null)
|
||||||
|
{
|
||||||
|
originalPath = (String)request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI);
|
||||||
|
originalQuery = (String)request.getAttribute(RequestDispatcher.INCLUDE_QUERY_STRING);
|
||||||
|
}
|
||||||
|
if (originalPath != null)
|
||||||
|
{
|
||||||
|
String originalURI = originalPath;
|
||||||
|
if (originalQuery != null)
|
||||||
|
originalURI += "?" + originalQuery;
|
||||||
|
proxyRequest.attribute(REQUEST_URI_ATTRIBUTE, originalURI);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.customizeProxyRequest(proxyRequest, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void customizeFastCGIHeaders(Request proxyRequest, HttpFields fastCGIHeaders)
|
||||||
|
{
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.REMOTE_ADDR, (String)proxyRequest.getAttributes().get(REMOTE_ADDR_ATTRIBUTE));
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.REMOTE_PORT, (String)proxyRequest.getAttributes().get(REMOTE_PORT_ATTRIBUTE));
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.SERVER_NAME, (String)proxyRequest.getAttributes().get(SERVER_NAME_ATTRIBUTE));
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE));
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE));
|
||||||
|
|
||||||
|
if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE)))
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.HTTPS, "on");
|
||||||
|
|
||||||
|
URI proxyRequestURI = proxyRequest.getURI();
|
||||||
|
String rawPath = proxyRequestURI.getRawPath();
|
||||||
|
String rawQuery = proxyRequestURI.getRawQuery();
|
||||||
|
|
||||||
|
String requestURI = (String)proxyRequest.getAttributes().get(REQUEST_URI_ATTRIBUTE);
|
||||||
|
if (requestURI == null)
|
||||||
|
{
|
||||||
|
requestURI = rawPath;
|
||||||
|
if (rawQuery != null)
|
||||||
|
requestURI += "?" + rawQuery;
|
||||||
|
}
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.REQUEST_URI, requestURI);
|
||||||
|
|
||||||
|
String scriptName = rawPath;
|
||||||
|
Matcher matcher = scriptPattern.matcher(rawPath);
|
||||||
|
if (matcher.matches())
|
||||||
|
{
|
||||||
|
// Expect at least one group in the regular expression.
|
||||||
|
scriptName = matcher.group(1);
|
||||||
|
|
||||||
|
// If there is a second group, map it to PATH_INFO.
|
||||||
|
if (matcher.groupCount() > 1)
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.PATH_INFO, matcher.group(2));
|
||||||
|
}
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.SCRIPT_NAME, scriptName);
|
||||||
|
|
||||||
|
String root = fastCGIHeaders.get(FCGI.Headers.DOCUMENT_ROOT);
|
||||||
|
fastCGIHeaders.put(FCGI.Headers.SCRIPT_FILENAME, root + scriptName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ProxyHttpClientTransportOverFCGI extends HttpClientTransportOverFCGI
|
||||||
|
{
|
||||||
|
public ProxyHttpClientTransportOverFCGI(String scriptRoot)
|
||||||
|
{
|
||||||
|
super(scriptRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void customize(Request request, HttpFields fastCGIHeaders)
|
||||||
|
{
|
||||||
|
super.customize(request, fastCGIHeaders);
|
||||||
|
customizeFastCGIHeaders(request, fastCGIHeaders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.proxy;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import javax.servlet.Filter;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.FilterConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired by nginx's try_files functionality
|
||||||
|
*/
|
||||||
|
public class TryFilesFilter implements Filter
|
||||||
|
{
|
||||||
|
public static final String FILES_INIT_PARAM = "files";
|
||||||
|
|
||||||
|
private String[] files;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(FilterConfig config) throws ServletException
|
||||||
|
{
|
||||||
|
String param = config.getInitParameter(FILES_INIT_PARAM);
|
||||||
|
if (param == null)
|
||||||
|
throw new ServletException(String.format("Missing mandatory parameter '%s'", FILES_INIT_PARAM));
|
||||||
|
files = param.split(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
HttpServletRequest httpRequest = (HttpServletRequest)request;
|
||||||
|
HttpServletResponse httpResponse = (HttpServletResponse)response;
|
||||||
|
|
||||||
|
for (int i = 0; i < files.length - 1; ++i)
|
||||||
|
{
|
||||||
|
String file = files[i];
|
||||||
|
String resolved = resolve(httpRequest, file);
|
||||||
|
|
||||||
|
URL url = request.getServletContext().getResource(resolved);
|
||||||
|
if (url == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (Files.isReadable(toPath(url)))
|
||||||
|
{
|
||||||
|
chain.doFilter(httpRequest, httpResponse);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The last one is the fallback
|
||||||
|
fallback(httpRequest, httpResponse, chain, files[files.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Path toPath(URL url) throws IOException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return Paths.get(url.toURI());
|
||||||
|
}
|
||||||
|
catch (URISyntaxException x)
|
||||||
|
{
|
||||||
|
throw new IOException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void fallback(HttpServletRequest request, HttpServletResponse response, FilterChain chain, String fallback) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
String resolved = resolve(request, fallback);
|
||||||
|
request.getServletContext().getRequestDispatcher(resolved).forward(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolve(HttpServletRequest request, String value)
|
||||||
|
{
|
||||||
|
String path = request.getServletPath();
|
||||||
|
String info = request.getPathInfo();
|
||||||
|
if (info != null)
|
||||||
|
path += info;
|
||||||
|
if (!path.startsWith("/"))
|
||||||
|
path = "/" + path;
|
||||||
|
return value.replaceAll("\\$path", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.proxy;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.spdy.api.SPDY;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.PushStrategy;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
public class DrupalSPDYFastCGIProxyServer
|
||||||
|
{
|
||||||
|
public static void main(String[] args) throws Exception
|
||||||
|
{
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
sslContextFactory.setEndpointIdentificationAlgorithm("");
|
||||||
|
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
|
||||||
|
sslContextFactory.setTrustStorePassword("storepwd");
|
||||||
|
|
||||||
|
Server server = new Server();
|
||||||
|
|
||||||
|
Map<Short, PushStrategy> pushStrategies = new HashMap<>();
|
||||||
|
pushStrategies.put(SPDY.V3, new ReferrerPushStrategy());
|
||||||
|
HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies);
|
||||||
|
connector.setPort(8443);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
// Drupal seems to only work on the root context,
|
||||||
|
// at least out of the box without additional plugins
|
||||||
|
|
||||||
|
String root = "/home/simon/programs/drupal-7.23";
|
||||||
|
|
||||||
|
ServletContextHandler context = new ServletContextHandler(server, "/");
|
||||||
|
context.setResourceBase(root);
|
||||||
|
context.setWelcomeFiles(new String[]{"index.php"});
|
||||||
|
|
||||||
|
// Serve static resources
|
||||||
|
ServletHolder defaultServlet = new ServletHolder(DefaultServlet.class);
|
||||||
|
defaultServlet.setName("default");
|
||||||
|
context.addServlet(defaultServlet, "/");
|
||||||
|
|
||||||
|
// FastCGI
|
||||||
|
ServletHolder fcgiServlet = new ServletHolder(FastCGIProxyServlet.class);
|
||||||
|
fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root);
|
||||||
|
fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000");
|
||||||
|
fcgiServlet.setInitParameter("prefix", "/");
|
||||||
|
fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+\\.php)");
|
||||||
|
context.addServlet(fcgiServlet, "*.php");
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.proxy;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.spdy.api.SPDY;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.PushStrategy;
|
||||||
|
import org.eclipse.jetty.spdy.server.http.ReferrerPushStrategy;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
|
|
||||||
|
public class WordPressSPDYFastCGIProxyServer
|
||||||
|
{
|
||||||
|
public static void main(String[] args) throws Exception
|
||||||
|
{
|
||||||
|
int port = 8080;
|
||||||
|
int tlsPort = 8443;
|
||||||
|
|
||||||
|
SslContextFactory sslContextFactory = new SslContextFactory();
|
||||||
|
sslContextFactory.setEndpointIdentificationAlgorithm("");
|
||||||
|
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
|
||||||
|
sslContextFactory.setKeyStorePassword("storepwd");
|
||||||
|
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
|
||||||
|
sslContextFactory.setTrustStorePassword("storepwd");
|
||||||
|
|
||||||
|
Server server = new Server();
|
||||||
|
|
||||||
|
Map<Short, PushStrategy> pushStrategies = new HashMap<>();
|
||||||
|
pushStrategies.put(SPDY.V3, new ReferrerPushStrategy());
|
||||||
|
HTTPSPDYServerConnector tlsConnector = new HTTPSPDYServerConnector(server, sslContextFactory, pushStrategies);
|
||||||
|
tlsConnector.setPort(tlsPort);
|
||||||
|
server.addConnector(tlsConnector);
|
||||||
|
HTTPSPDYServerConnector connector = new HTTPSPDYServerConnector(server, null, pushStrategies);
|
||||||
|
connector.setPort(port);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
String root = "/home/simon/programs/wordpress-3.7.1";
|
||||||
|
|
||||||
|
ServletContextHandler context = new ServletContextHandler(server, "/wp");
|
||||||
|
context.setResourceBase(root);
|
||||||
|
context.setWelcomeFiles(new String[]{"index.php"});
|
||||||
|
|
||||||
|
// Serve static resources
|
||||||
|
ServletHolder defaultServlet = new ServletHolder("default", DefaultServlet.class);
|
||||||
|
context.addServlet(defaultServlet, "/");
|
||||||
|
|
||||||
|
FilterHolder tryFilesFilter = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
// tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path $path/index.php"); // Permalink /?p=123
|
||||||
|
tryFilesFilter.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path /index.php?p=$path"); // Permalink /%year%/%monthnum%/%postname%
|
||||||
|
|
||||||
|
// FastCGI
|
||||||
|
ServletHolder fcgiServlet = context.addServlet(FastCGIProxyServlet.class, "*.php");
|
||||||
|
fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, root);
|
||||||
|
fcgiServlet.setInitParameter("proxyTo", "http://localhost:9000");
|
||||||
|
fcgiServlet.setInitParameter("prefix", "/");
|
||||||
|
fcgiServlet.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)");
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||||
|
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||||
|
#org.eclipse.jetty.fcgi.LEVEL=DEBUG
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>fcgi-server</artifactId>
|
||||||
|
<name>Jetty :: FastCGI :: Server</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<version>${jetty-version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,204 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.server;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.server.HttpInput;
|
||||||
|
import org.eclipse.jetty.server.HttpTransport;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class HttpChannelOverFCGI extends HttpChannel<ByteBuffer>
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(HttpChannelOverFCGI.class);
|
||||||
|
|
||||||
|
private final List<HttpField> fields = new ArrayList<>();
|
||||||
|
private final Dispatcher dispatcher;
|
||||||
|
private String method;
|
||||||
|
private String path;
|
||||||
|
private String query;
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
public HttpChannelOverFCGI(Connector connector, HttpConfiguration configuration, EndPoint endPoint, HttpTransport transport, HttpInput<ByteBuffer> input)
|
||||||
|
{
|
||||||
|
super(connector, configuration, endPoint, transport, input);
|
||||||
|
this.dispatcher = new Dispatcher(connector.getExecutor(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void header(HttpField field)
|
||||||
|
{
|
||||||
|
if (FCGI.Headers.REQUEST_METHOD.equalsIgnoreCase(field.getName()))
|
||||||
|
method = field.getValue();
|
||||||
|
else if (FCGI.Headers.DOCUMENT_URI.equalsIgnoreCase(field.getName()))
|
||||||
|
path = field.getValue();
|
||||||
|
else if (FCGI.Headers.QUERY_STRING.equalsIgnoreCase(field.getName()))
|
||||||
|
query = field.getValue();
|
||||||
|
else if (FCGI.Headers.SERVER_PROTOCOL.equalsIgnoreCase(field.getName()))
|
||||||
|
version = field.getValue();
|
||||||
|
else
|
||||||
|
fields.add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean headerComplete()
|
||||||
|
{
|
||||||
|
String uri = path;
|
||||||
|
if (query != null && query.length() > 0)
|
||||||
|
uri += "?" + query;
|
||||||
|
startRequest(HttpMethod.fromString(method), method, ByteBuffer.wrap(uri.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
HttpVersion.fromString(version));
|
||||||
|
|
||||||
|
for (HttpField fcgiField : fields)
|
||||||
|
{
|
||||||
|
HttpField httpField = convertHeader(fcgiField);
|
||||||
|
if (httpField != null)
|
||||||
|
parsedHeader(httpField);
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.headerComplete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpField convertHeader(HttpField field)
|
||||||
|
{
|
||||||
|
String name = field.getName();
|
||||||
|
if (name.startsWith("HTTP_"))
|
||||||
|
{
|
||||||
|
// Converts e.g. "HTTP_ACCEPT_ENCODING" to "Accept-Encoding"
|
||||||
|
String[] parts = name.split("_");
|
||||||
|
StringBuilder httpName = new StringBuilder();
|
||||||
|
for (int i = 1; i < parts.length; ++i)
|
||||||
|
{
|
||||||
|
if (i > 1)
|
||||||
|
httpName.append("-");
|
||||||
|
String part = parts[i];
|
||||||
|
httpName.append(Character.toUpperCase(part.charAt(0)));
|
||||||
|
httpName.append(part.substring(1).toLowerCase(Locale.ENGLISH));
|
||||||
|
}
|
||||||
|
return new HttpField(httpName.toString(), field.getValue());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void dispatch()
|
||||||
|
{
|
||||||
|
dispatcher.dispatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Dispatcher implements Runnable
|
||||||
|
{
|
||||||
|
private final AtomicReference<State> state = new AtomicReference<>(State.IDLE);
|
||||||
|
private final Executor executor;
|
||||||
|
private final Runnable runnable;
|
||||||
|
|
||||||
|
private Dispatcher(Executor executor, Runnable runnable)
|
||||||
|
{
|
||||||
|
this.executor = executor;
|
||||||
|
this.runnable = runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispatch()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
State current = state.get();
|
||||||
|
LOG.debug("Dispatching, state={}", current);
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case IDLE:
|
||||||
|
{
|
||||||
|
if (!state.compareAndSet(current, State.DISPATCH))
|
||||||
|
continue;
|
||||||
|
executor.execute(this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case DISPATCH:
|
||||||
|
case EXECUTE:
|
||||||
|
{
|
||||||
|
if (state.compareAndSet(current, State.SCHEDULE))
|
||||||
|
return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case SCHEDULE:
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
State current = state.get();
|
||||||
|
LOG.debug("Running, state={}", current);
|
||||||
|
switch (current)
|
||||||
|
{
|
||||||
|
case DISPATCH:
|
||||||
|
{
|
||||||
|
if (state.compareAndSet(current, State.EXECUTE))
|
||||||
|
runnable.run();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case EXECUTE:
|
||||||
|
{
|
||||||
|
if (state.compareAndSet(current, State.IDLE))
|
||||||
|
return;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case SCHEDULE:
|
||||||
|
{
|
||||||
|
if (state.compareAndSet(current, State.DISPATCH))
|
||||||
|
continue;
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State
|
||||||
|
{
|
||||||
|
IDLE, DISPATCH, EXECUTE, SCHEDULE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.server;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Generator;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
|
||||||
|
import org.eclipse.jetty.http.HttpGenerator;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.server.HttpTransport;
|
||||||
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
|
public class HttpTransportOverFCGI implements HttpTransport
|
||||||
|
{
|
||||||
|
private final ServerGenerator generator;
|
||||||
|
private final Flusher flusher;
|
||||||
|
private final int request;
|
||||||
|
private volatile boolean head;
|
||||||
|
|
||||||
|
public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request)
|
||||||
|
{
|
||||||
|
this.generator = new ServerGenerator(byteBufferPool);
|
||||||
|
this.flusher = flusher;
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
|
{
|
||||||
|
boolean head = this.head = info.isHead();
|
||||||
|
if (head)
|
||||||
|
{
|
||||||
|
if (lastContent)
|
||||||
|
{
|
||||||
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
|
Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
||||||
|
flusher.flush(headersResult, contentResult);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
|
info.getHttpFields(), callback);
|
||||||
|
flusher.flush(headersResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(),
|
||||||
|
info.getHttpFields(), new Callback.Adapter());
|
||||||
|
Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback);
|
||||||
|
flusher.flush(headersResult, contentResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void send(ByteBuffer content, boolean lastContent, Callback callback)
|
||||||
|
{
|
||||||
|
if (head)
|
||||||
|
{
|
||||||
|
if (lastContent)
|
||||||
|
{
|
||||||
|
Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback);
|
||||||
|
flusher.flush(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Skip content generation
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback);
|
||||||
|
flusher.flush(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void completed()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,179 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.server;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.fcgi.FCGI;
|
||||||
|
import org.eclipse.jetty.fcgi.generator.Flusher;
|
||||||
|
import org.eclipse.jetty.fcgi.parser.ServerParser;
|
||||||
|
import org.eclipse.jetty.http.HttpField;
|
||||||
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.ByteBufferQueuedHttpInput;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
public class ServerFCGIConnection extends AbstractConnection
|
||||||
|
{
|
||||||
|
private static final Logger LOG = Log.getLogger(ServerFCGIConnection.class);
|
||||||
|
|
||||||
|
private final ConcurrentMap<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
|
||||||
|
private final Connector connector;
|
||||||
|
private final Flusher flusher;
|
||||||
|
private final HttpConfiguration configuration;
|
||||||
|
private final ServerParser parser;
|
||||||
|
|
||||||
|
public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration)
|
||||||
|
{
|
||||||
|
super(endPoint, connector.getExecutor());
|
||||||
|
this.connector = connector;
|
||||||
|
this.flusher = new Flusher(endPoint);
|
||||||
|
this.configuration = configuration;
|
||||||
|
this.parser = new ServerParser(new ServerListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onOpen()
|
||||||
|
{
|
||||||
|
super.onOpen();
|
||||||
|
fillInterested();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFillable()
|
||||||
|
{
|
||||||
|
EndPoint endPoint = getEndPoint();
|
||||||
|
ByteBufferPool bufferPool = connector.getByteBufferPool();
|
||||||
|
ByteBuffer buffer = bufferPool.acquire(configuration.getResponseHeaderSize(), true);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int read = endPoint.fill(buffer);
|
||||||
|
if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'
|
||||||
|
LOG.debug("Read {} bytes from {}", read, endPoint);
|
||||||
|
if (read > 0)
|
||||||
|
{
|
||||||
|
parse(buffer);
|
||||||
|
}
|
||||||
|
else if (read == 0)
|
||||||
|
{
|
||||||
|
fillInterested();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
LOG.debug(x);
|
||||||
|
// TODO: fail and close ?
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
bufferPool.release(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parse(ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
while (buffer.hasRemaining())
|
||||||
|
parser.parse(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdown()
|
||||||
|
{
|
||||||
|
flusher.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ServerListener implements ServerParser.Listener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onStart(int request, FCGI.Role role, int flags)
|
||||||
|
{
|
||||||
|
// TODO: handle flags
|
||||||
|
HttpChannelOverFCGI channel = new HttpChannelOverFCGI(connector, configuration, getEndPoint(),
|
||||||
|
new HttpTransportOverFCGI(connector.getByteBufferPool(), flusher, request), new ByteBufferQueuedHttpInput());
|
||||||
|
HttpChannelOverFCGI existing = channels.putIfAbsent(request, channel);
|
||||||
|
if (existing != null)
|
||||||
|
throw new IllegalStateException();
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} start on {}", request, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeader(int request, HttpField field)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} header {} on {}", request, field, channel);
|
||||||
|
if (channel != null)
|
||||||
|
channel.header(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHeaders(int request)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} headers on {}", request, channel);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel.headerComplete())
|
||||||
|
channel.dispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.get(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} {} content {} on {}", request, stream, buffer, channel);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel.content(buffer))
|
||||||
|
channel.dispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(int request)
|
||||||
|
{
|
||||||
|
HttpChannelOverFCGI channel = channels.remove(request);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request {} end on {}", request, channel);
|
||||||
|
if (channel != null)
|
||||||
|
{
|
||||||
|
if (channel.messageComplete())
|
||||||
|
channel.dispatch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
//
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
//
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
//
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
//
|
||||||
|
|
||||||
|
package org.eclipse.jetty.fcgi.server;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.Connection;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.server.AbstractConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Connector;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
|
||||||
|
public class ServerFCGIConnectionFactory extends AbstractConnectionFactory
|
||||||
|
{
|
||||||
|
private final HttpConfiguration configuration;
|
||||||
|
|
||||||
|
public ServerFCGIConnectionFactory(HttpConfiguration configuration)
|
||||||
|
{
|
||||||
|
super("fcgi/1.0");
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connection newConnection(Connector connector, EndPoint endPoint)
|
||||||
|
{
|
||||||
|
return new ServerFCGIConnection(connector, endPoint, configuration);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<parent>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-project</artifactId>
|
||||||
|
<version>9.1.1.v20140108</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.eclipse.jetty.fcgi</groupId>
|
||||||
|
<artifactId>fcgi-parent</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>Jetty :: FastCGI</name>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<jetty-version>9.1.1.v20140108</jetty-version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>fcgi-core</module>
|
||||||
|
<module>fcgi-http-client-transport</module>
|
||||||
|
<module>fcgi-server</module>
|
||||||
|
<module>fcgi-proxy</module>
|
||||||
|
<module>fcgi-distribution</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!--
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<descriptorRefs>
|
||||||
|
<descriptorRef>config</descriptorRef>
|
||||||
|
</descriptorRefs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<extensions>true</extensions>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>manifest</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Export-Package>${bundle-symbolic-name}.*;version="9.1"</Export-Package>
|
||||||
|
<Import-Package>org.eclipse.jetty.*;version="[9.0,10.0)",*</Import-Package>
|
||||||
|
<_nouses>true</_nouses>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<archive>
|
||||||
|
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
||||||
|
</archive>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
-->
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
</project>
|
Loading…
Reference in New Issue