11package fr .breadeater .javaphp ;
22
3- import com .sun .net .httpserver .Headers ;
4-
53import java .io .*;
64import java .net .*;
7- import java .nio .channels .Channels ;
85import java .nio .channels .SocketChannel ;
96import java .util .Map ;
10- import java .util .Objects ;
7+ import java .util .concurrent .* ;
118import java .util .function .Consumer ;
12- import java .util .regex .Pattern ;
139
1410public class JavaPHP {
11+ private static ExecutorService threadPool = Executors .newCachedThreadPool ();
12+
13+ private SocketChannel socketChannel ;
1514 private Consumer <Exception > onError = null ;
1615 private InetSocketAddress address ;
17-
18-
19- private boolean useUnixSocket = false ;
2016 private File unixSocketFile ;
2117
18+ private boolean useThreadPool = false ;
19+
2220
2321 /**
2422 * Creates a new JavaPHP instance.
2523 *
2624 * <p>JavaPHP communicates with a PHP FastCGI / PHP FPM server over TCP socket.</p>
2725 *
28- * @param address The {@link InetSocketAddress} of the PHP FastCGI / PHP FPM server. {@link InetSocketAddress} with 127.0.0.1 as hostname is <strong>STRONGLY</strong> recommended.
26+ * @param address The {@link InetSocketAddress} of the PHP FastCGI / PHP- FPM server. {@link InetSocketAddress} with 127.0.0.1 as hostname is <strong>STRONGLY</strong> recommended.
2927 *
30- * @implNote <strong>Note:</strong> Unix sockets are not supported. Make sure you start a PHP FastCGI / PHP FPM server (e.g., using <code>php-cgi -b 127.0.0.1:PORT</code> or use PHP FPM server)
28+ * @implNote <strong>Note:</strong> Make sure you start a PHP FastCGI / PHP FPM server (e.g., using <code>php-cgi -b 127.0.0.1:PORT</code> or use PHP FPM server)
3129 * and bind it to <code>localhost</code> to avoid exposing it to external connections.
3230 */
33- public JavaPHP (InetSocketAddress address ){
31+ public JavaPHP (InetSocketAddress address ) throws Exception {
32+ this .socketChannel = SocketChannel .open (StandardProtocolFamily .INET );
3433 this .address = address ;
3534 }
3635
3736
3837 /**
39- * Uses Unix Socket instead of TCP/IP for running PHP files with FastCGI
38+ * Should this JavaPHP instance use a Thread Pool to run PHP scripts ?
4039 *
41- * @implNote <strong>Note:</strong> This only works on Linux/WSL using root, won't work under Windows !
40+ * @param useThreadPool Enable / Disable the usage of a Thread Pool by this instance of JavaPHP.
4241 *
43- * @param unixSocket Should the Unix Socket be used instead of TCP/IP to run PHP files.<br>
44- * @param unixSocketFile The file pointing to the Unix Socket (e.g /run/php/php8.4-fpm.sock or /run/php/php8-fpm.sock).<br>
42+ * @apiNote <strong>Note: </strong> By default: the thread pool is a {@link Executors#newCachedThreadPool()}, to change it, use {@link #setThreadPool(ExecutorService threadPool)} function.
43+ */
44+ public void useThreadPool (boolean useThreadPool ){
45+ this .useThreadPool = useThreadPool ;
46+ }
47+
48+
49+ /**
50+ * Sets a new Thread Pool that all JavaPHP instances can use.
51+ * @param threadPool The Thread Pool to use.
4552 */
46- public void useUnixSocket (boolean unixSocket , File unixSocketFile ){
47- try {
48- String osname = System .getProperty ("os.name" ).toLowerCase ();
49- String user = System .getProperty ("user.name" );
50-
51- if (!osname .contains ("linux" )) throw new UnsupportedOperationException ("Unix Socket cannot be used on other Operating Systems than Linux/WSL !" );
52- if (!user .equals ("root" )) throw new IllegalAccessException ("Unix Socket requires root privileges !" );
53-
54- this .unixSocketFile = unixSocketFile ;
55- this .useUnixSocket = unixSocket ;
56- } catch (Exception error ){
57- if (this .onError != null ){
58- this .onError .accept (error );
59- } else throw new RuntimeException (error );
60- }
53+ public void setThreadPool (ExecutorService threadPool ){
54+ JavaPHP .threadPool = threadPool ;
55+ }
56+
57+
58+ /**
59+ * Uses Unix Socket instead of TCP/IP to communicate with PHP FastCGI (Linux only)
60+ * @param unixSocketFile The Unix Socket file to use.
61+ * @throws IllegalAccessException If the current user is not root.
62+ * @throws UnsupportedOperationException If the current OS is not Linux.
63+ * @throws IOException If an error happens when openning the {@link SocketChannel}.
64+ */
65+ public void useUnixSocket (File unixSocketFile ) throws IllegalAccessException , IOException {
66+ String osname = System .getProperty ("os.name" ).toLowerCase ();
67+ String user = System .getProperty ("user.name" );
68+
69+ if (!osname .contains ("linux" )) throw new UnsupportedOperationException ("Unix Socket cannot be used on other Operating Systems than Linux/WSL !" );
70+ if (!user .equals ("root" )) throw new IllegalAccessException ("Unix Socket requires root privileges !" );
71+
72+ this .socketChannel = SocketChannel .open (StandardProtocolFamily .UNIX );
73+ this .unixSocketFile = unixSocketFile ;
6174 }
6275
6376
6477 /**
6578 * Sets a {@link Consumer} function to execute when an error is being thrown.
66- *
6779 * @param callback The function to be executed if an error is thrown.
6880 */
6981 public void onError (Consumer <Exception > callback ){
@@ -74,102 +86,42 @@ public void onError(Consumer<Exception> callback){
7486 /**
7587 * Runs a PHP file on the PHP FastCGI server and returns result.
7688 *
77- * @param runOptions Options to use when running an PHP file, basically specifies FastCGI params (e.g <code>$_SERVER[" REMOTE_ADDR"]</code> )
89+ * @param options Options to use when running an PHP file, basically specifies FastCGI params (e.g: REMOTE_ADDR, HTTPS, etc... )
7890 * @param request An instance of {@link Request} containing Headers, Body, etc...
7991 */
80- public Response run (Options runOptions , Request request ){
81- Response response = new Response ();
82- ProtocolFamily protocol = StandardProtocolFamily .INET ;
83- SocketAddress address = this .address ;
84-
85- if (this .useUnixSocket ){
86- if (this .unixSocketFile .exists () && this .validatePHPUnixSocketPath (this .unixSocketFile )){
87-
88- address = UnixDomainSocketAddress .of (this .unixSocketFile .toPath ());
89- protocol = StandardProtocolFamily .UNIX ;
92+ public Response run (Options options , Request request ) throws Exception {
93+ Callable <Response > runLogic = () -> {
94+ try {
95+ Map <String , String > fastCGIheaders = JavaPHPUtils .setFastCGIParams (options , request );
96+ String reqbody = "" ;
9097
91- } else {
92- throw new IllegalArgumentException ("Unix Socket path is invalid or does not exists (file name must be formatted like this: phpX.X-fpm.sock) !" );
93- }
94- }
95-
96- Objects .requireNonNull (address );
97- Objects .requireNonNull (runOptions );
98- Objects .requireNonNull (request );
99-
100- try {
101- SocketChannel socket = SocketChannel .open (protocol );
102-
103- socket .connect (address );
104-
105- Map <String , String > fastCGIheaders = FastCGIUtils .setFastCGIParams (runOptions , request );
106- OutputStream out = Channels .newOutputStream (socket );
107- InputStream in = Channels .newInputStream (socket );
98+ if (request .body != null ) reqbody = request .body ;
10899
109- byte [] reqbody = new byte [0 ];
100+ if (this .unixSocketFile != null ){
101+ if (!this .unixSocketFile .exists ()) throw new IllegalArgumentException ("Unix Socket path is invalid or does not exists (file name must be formatted like this: phpX.X-fpm.sock) !" );
110102
111- if (request .getRequestBody () != null ) reqbody = request .getRequestBody ().getBytes ();
103+ this .socketChannel .connect (UnixDomainSocketAddress .of (this .unixSocketFile .getCanonicalPath ()));
104+ } else this .socketChannel .connect (this .address );
112105
113- out . write (FastCGIUtils .buildRequest (1 ));
114- out . write (FastCGIUtils .buildParams (false , 1 , fastCGIheaders ));
115- out . write (FastCGIUtils .buildParams (true , 1 , fastCGIheaders ));
116- out . write (FastCGIUtils .buildStdin (1 , reqbody ));
117- out . write (FastCGIUtils . buildEmptyStdin ( 1 ));
106+ this . socketChannel . write (JavaPHPUtils .buildRequest ());
107+ this . socketChannel . write (JavaPHPUtils .buildParams (false , fastCGIheaders ));
108+ this . socketChannel . write (JavaPHPUtils .buildParams (true , fastCGIheaders ));
109+ this . socketChannel . write (JavaPHPUtils .buildStdin (reqbody . getBytes () ));
110+ this . socketChannel . write (JavaPHPUtils . buildStdin ( new byte [ 0 ] ));
118111
119- String fastCGIresponse = FastCGIUtils .parseFastCGIRequest (socket , in );
120- String line ;
112+ BufferedReader responseReader = JavaPHPUtils .parseFastCGIRequest (this .socketChannel );
121113
122- BufferedReader responseReader = new BufferedReader ( new CharArrayReader ( fastCGIresponse . toCharArray ()) );
123- StringBuilder body = new StringBuilder ();
124- Headers headers = new Headers ( );
114+ return JavaPHPUtils . parseResponse ( responseReader );
115+ } catch ( Exception err ){
116+ if ( this . onError != null ) this . onError . accept ( err );
125117
126- while ((line = responseReader .readLine ()) != null ){
127- if (line .isEmpty ()){
128- String bodyLine ;
129-
130- while ((bodyLine = responseReader .readLine ()) != null ) body .append (bodyLine ).append ("\r \n " );
131- break ;
132- }
133-
134- String [] splitheader = line .split (": " , 2 );
135-
136- headers .add (splitheader [0 ], splitheader [1 ]);
118+ return null ;
137119 }
120+ };
138121
139- headers .forEach ((name , value ) -> {
140- if (name .equals ("Status" )){
141- String [] splitStatus = value .get (0 ).split (" " );
142-
143- response .statuscode = Integer .parseInt (splitStatus [0 ]);
144- }
145- });
146-
147- headers .remove ("Status" );
148-
149- response .headers = headers ;
150- response .body = body .toString ();
151- } catch (Exception err ){
152- if (this .onError != null ) this .onError .accept (err );
153-
154- return null ;
155- }
156-
157- return response ;
158- }
159-
160-
161- /**
162- * Checks if the Unix Socket file exists and if the filename of the PHP Unix Socket ends is formatted correctly (phpX-fpm.sock or phpX.X-fpm.sock).
163- * @param file The file to be checked.
164- * @return True / False depending on if the filename is correctly formatted and if the Unix Socket file exists.
165- */
166- private boolean validatePHPUnixSocketPath (File file ){
167- final Pattern SOCKET_PATTERN = Pattern .compile ("^php\\ d+(\\ .\\ d+)?-fpm\\ .sock$" );
168-
169- if (file == null ) return false ;
170- if (!file .exists ()) return false ;
171-
172- return SOCKET_PATTERN .matcher (file .getName ()).matches ();
122+ if (this .useThreadPool ){
123+ return JavaPHP .threadPool .submit (runLogic ).get ();
124+ } else return runLogic .call ();
173125 }
174126
175127
0 commit comments