Skip to content

Commit 19b5e0d

Browse files
committed
Added async execution and code is more optimized
1 parent 5629597 commit 19b5e0d

File tree

7 files changed

+235
-232
lines changed

7 files changed

+235
-232
lines changed

pom.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66

77
<groupId>fr.breadeater</groupId>
8-
<artifactId>JavaPHP</artifactId>
8+
<artifactId>javaphp</artifactId>
99
<version>2.2.1</version>
1010

1111
<properties>
@@ -16,9 +16,9 @@
1616

1717
<distributionManagement>
1818
<repository>
19-
<id>github</id>
20-
<name>GitHub Packages</name>
21-
<url>https://maven.pkg.github.com/BreadEaterYT/JavaPHP</url>
19+
<id>cdnmaven</id>
20+
<name>BreadEaterCDN Maven</name>
21+
<url>https://cdn.breadeater.fr/maven</url>
2222
</repository>
2323
</distributionManagement>
2424

src/main/java/fr/breadeater/javaphp/JavaPHP.java

Lines changed: 68 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,81 @@
11
package fr.breadeater.javaphp;
22

3-
import com.sun.net.httpserver.Headers;
4-
53
import java.io.*;
64
import java.net.*;
7-
import java.nio.channels.Channels;
85
import java.nio.channels.SocketChannel;
96
import java.util.Map;
10-
import java.util.Objects;
7+
import java.util.concurrent.*;
118
import java.util.function.Consumer;
12-
import java.util.regex.Pattern;
139

1410
public 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

Comments
 (0)