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