2020-10-12 20:56:58 +00:00
|
|
|
/* ========================================================================
|
|
|
|
* PlantUML : a free UML diagram generator
|
|
|
|
* ========================================================================
|
|
|
|
*
|
|
|
|
* (C) Copyright 2009-2020, Arnaud Roques
|
|
|
|
*
|
|
|
|
* Project Info: http://plantuml.com
|
|
|
|
*
|
|
|
|
* If you like this project or if you find it useful, you can support us at:
|
|
|
|
*
|
|
|
|
* http://plantuml.com/patreon (only 1$ per month!)
|
|
|
|
* http://plantuml.com/paypal
|
|
|
|
*
|
|
|
|
* This file is part of PlantUML.
|
|
|
|
*
|
|
|
|
* PlantUML is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* PlantUML distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
|
|
|
* License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public
|
|
|
|
* License along with this library; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
|
|
|
|
* USA.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Original Author: Arnaud Roques
|
|
|
|
*
|
|
|
|
* Highly inspired from https://www.ssaurel.com/blog/create-a-simple-http-web-server-in-java
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
package net.sourceforge.plantuml.picoweb;
|
|
|
|
|
2021-03-04 18:11:16 +00:00
|
|
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
|
|
import static net.sourceforge.plantuml.ErrorUmlType.SYNTAX_ERROR;
|
|
|
|
|
2021-02-12 14:09:28 +00:00
|
|
|
import java.io.BufferedInputStream;
|
2020-10-12 20:56:58 +00:00
|
|
|
import java.io.BufferedOutputStream;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.OutputStream;
|
2021-02-12 14:09:28 +00:00
|
|
|
import java.io.PrintWriter;
|
2021-02-12 06:19:03 +00:00
|
|
|
import java.net.InetAddress;
|
2020-10-12 20:56:58 +00:00
|
|
|
import java.net.ServerSocket;
|
|
|
|
import java.net.Socket;
|
2021-02-12 14:09:28 +00:00
|
|
|
import java.util.Collections;
|
2020-10-12 20:56:58 +00:00
|
|
|
import java.util.Date;
|
2020-11-21 17:33:24 +00:00
|
|
|
import java.util.List;
|
2020-10-12 20:56:58 +00:00
|
|
|
|
2020-11-21 17:33:24 +00:00
|
|
|
import net.sourceforge.plantuml.BlockUml;
|
|
|
|
import net.sourceforge.plantuml.ErrorUml;
|
2020-10-12 20:56:58 +00:00
|
|
|
import net.sourceforge.plantuml.FileFormat;
|
|
|
|
import net.sourceforge.plantuml.FileFormatOption;
|
2021-02-12 14:09:28 +00:00
|
|
|
import net.sourceforge.plantuml.LineLocationImpl;
|
|
|
|
import net.sourceforge.plantuml.Option;
|
2020-10-12 20:56:58 +00:00
|
|
|
import net.sourceforge.plantuml.SourceStringReader;
|
2021-02-12 14:09:28 +00:00
|
|
|
import net.sourceforge.plantuml.StringLocated;
|
2020-11-21 17:33:24 +00:00
|
|
|
import net.sourceforge.plantuml.StringUtils;
|
2020-10-12 20:56:58 +00:00
|
|
|
import net.sourceforge.plantuml.code.Transcoder;
|
|
|
|
import net.sourceforge.plantuml.code.TranscoderUtil;
|
2020-11-21 17:33:24 +00:00
|
|
|
import net.sourceforge.plantuml.core.Diagram;
|
|
|
|
import net.sourceforge.plantuml.core.ImageData;
|
|
|
|
import net.sourceforge.plantuml.error.PSystemError;
|
2021-02-12 14:09:28 +00:00
|
|
|
import net.sourceforge.plantuml.error.PSystemErrorUtils;
|
2020-11-21 17:33:24 +00:00
|
|
|
import net.sourceforge.plantuml.graphic.QuoteUtils;
|
2021-05-08 01:07:20 +00:00
|
|
|
import net.sourceforge.plantuml.security.SFile;
|
2020-11-21 17:33:24 +00:00
|
|
|
import net.sourceforge.plantuml.version.Version;
|
2020-10-12 20:56:58 +00:00
|
|
|
|
|
|
|
public class PicoWebServer implements Runnable {
|
|
|
|
|
|
|
|
private final Socket connect;
|
|
|
|
|
|
|
|
public PicoWebServer(Socket c) {
|
|
|
|
this.connect = c;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void main(String[] args) throws IOException {
|
2021-02-12 06:19:03 +00:00
|
|
|
startServer(8080, null);
|
2020-10-12 20:56:58 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 06:19:03 +00:00
|
|
|
public static void startServer(final int port, final String bindAddress) throws IOException {
|
|
|
|
final InetAddress bindAddress1 = bindAddress == null ? null : InetAddress.getByName(bindAddress);
|
|
|
|
final ServerSocket serverConnect = new ServerSocket(port, 50, bindAddress1);
|
|
|
|
System.err.println("webPort=" + serverConnect.getLocalPort());
|
2021-02-12 14:09:28 +00:00
|
|
|
serverLoop(serverConnect);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void serverLoop(final ServerSocket serverConnect) throws IOException {
|
2020-10-12 20:56:58 +00:00
|
|
|
while (true) {
|
|
|
|
final PicoWebServer myServer = new PicoWebServer(serverConnect.accept());
|
|
|
|
final Thread thread = new Thread(myServer);
|
|
|
|
thread.start();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void run() {
|
2021-02-12 14:09:28 +00:00
|
|
|
BufferedInputStream in = null;
|
2020-10-12 20:56:58 +00:00
|
|
|
BufferedOutputStream out = null;
|
|
|
|
|
|
|
|
try {
|
2021-02-12 14:09:28 +00:00
|
|
|
in = new BufferedInputStream(connect.getInputStream());
|
2020-10-12 20:56:58 +00:00
|
|
|
out = new BufferedOutputStream(connect.getOutputStream());
|
|
|
|
|
2021-02-12 14:09:28 +00:00
|
|
|
final ReceivedHTTPRequest request = ReceivedHTTPRequest.fromStream(in);
|
|
|
|
if (request.getMethod().equals("GET")) {
|
|
|
|
if (request.getPath().startsWith("/png/") && handleGET(request, out, FileFormat.PNG))
|
2021-02-02 10:12:15 +00:00
|
|
|
return;
|
2021-02-12 14:09:28 +00:00
|
|
|
if (request.getPath().startsWith("/plantuml/png/") && handleGET(request, out, FileFormat.PNG))
|
2020-10-12 20:56:58 +00:00
|
|
|
return;
|
2021-02-12 14:09:28 +00:00
|
|
|
if (request.getPath().startsWith("/svg/") && handleGET(request, out, FileFormat.SVG))
|
2021-02-02 10:12:15 +00:00
|
|
|
return;
|
2021-02-12 14:09:28 +00:00
|
|
|
if (request.getPath().startsWith("/plantuml/svg/") && handleGET(request, out, FileFormat.SVG))
|
2020-10-12 20:56:58 +00:00
|
|
|
return;
|
2021-02-12 14:09:28 +00:00
|
|
|
} else if (request.getMethod().equals("POST") && request.getPath().equals("/render")) {
|
|
|
|
handleRenderRequest(request, out);
|
|
|
|
return;
|
2020-10-12 20:56:58 +00:00
|
|
|
}
|
|
|
|
write(out, "HTTP/1.1 302 Found");
|
|
|
|
write(out, "Location: /plantuml/png/oqbDJyrBuGh8ISmh2VNrKGZ8JCuFJqqAJYqgIotY0aefG5G00000");
|
|
|
|
write(out, "");
|
|
|
|
out.flush();
|
|
|
|
|
|
|
|
} catch (Throwable e) {
|
2021-02-12 14:09:28 +00:00
|
|
|
try {
|
|
|
|
sendError(e, out);
|
2021-03-04 18:11:16 +00:00
|
|
|
} catch (Throwable e1) {
|
2021-02-12 14:09:28 +00:00
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2020-10-12 20:56:58 +00:00
|
|
|
} finally {
|
|
|
|
try {
|
|
|
|
in.close();
|
|
|
|
out.close();
|
|
|
|
connect.close();
|
|
|
|
} catch (Throwable e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-04 18:11:16 +00:00
|
|
|
private boolean handleGET(ReceivedHTTPRequest request, BufferedOutputStream out, final FileFormat format)
|
|
|
|
throws IOException {
|
2021-02-12 14:09:28 +00:00
|
|
|
final int x = request.getPath().lastIndexOf('/');
|
|
|
|
final String compressed = request.getPath().substring(x + 1);
|
2020-10-12 20:56:58 +00:00
|
|
|
final Transcoder transcoder = TranscoderUtil.getDefaultTranscoderProtected();
|
|
|
|
final String source = transcoder.decode(compressed);
|
2020-11-21 17:33:24 +00:00
|
|
|
final SourceStringReader ssr = new SourceStringReader(source);
|
|
|
|
|
|
|
|
final List<BlockUml> blocks = ssr.getBlocks();
|
|
|
|
if (blocks.size() > 0) {
|
2021-02-12 14:09:28 +00:00
|
|
|
final FileFormatOption fileFormatOption = new FileFormatOption(format);
|
2020-11-21 17:33:24 +00:00
|
|
|
final Diagram system = blocks.get(0).getDiagram();
|
|
|
|
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
2021-02-12 14:09:28 +00:00
|
|
|
final ImageData imageData = system.exportDiagram(os, 0, fileFormatOption);
|
2020-11-21 17:33:24 +00:00
|
|
|
os.close();
|
2021-03-04 18:11:16 +00:00
|
|
|
sendDiagram(out, system, fileFormatOption, httpReturnCode(imageData.getStatus()), imageData,
|
|
|
|
os.toByteArray());
|
2020-11-21 17:33:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2020-10-12 20:56:58 +00:00
|
|
|
}
|
|
|
|
|
2021-02-12 14:09:28 +00:00
|
|
|
private void handleRenderRequest(ReceivedHTTPRequest request, BufferedOutputStream out) throws Exception {
|
|
|
|
if (request.getBody().length == 0) {
|
|
|
|
throw new BadRequest400("No request body");
|
|
|
|
}
|
|
|
|
|
|
|
|
final RenderRequest renderRequest;
|
|
|
|
try {
|
|
|
|
renderRequest = RenderRequest.fromJson(new String(request.getBody(), UTF_8));
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new BadRequest400("Error parsing request json: " + e.getMessage(), e);
|
|
|
|
}
|
2021-05-08 01:07:20 +00:00
|
|
|
|
|
|
|
handleRenderRequest(renderRequest, out);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void handleRenderRequest(RenderRequest renderRequest, BufferedOutputStream out) throws Exception {
|
2021-02-12 14:09:28 +00:00
|
|
|
|
|
|
|
final Option option = new Option(renderRequest.getOptions());
|
|
|
|
|
2021-03-04 18:11:16 +00:00
|
|
|
final String source = renderRequest.getSource().startsWith("@start") ? renderRequest.getSource()
|
2021-02-12 14:09:28 +00:00
|
|
|
: "@startuml\n" + renderRequest.getSource() + "\n@enduml";
|
|
|
|
|
2021-05-08 01:07:20 +00:00
|
|
|
final SFile newCurrentDir = option.getFileDir() == null ? null : new SFile(option.getFileDir());
|
2021-09-13 05:18:15 +00:00
|
|
|
final SourceStringReader ssr = new SourceStringReader(option.getDefaultDefines(), source, UTF_8,
|
2021-05-08 01:07:20 +00:00
|
|
|
option.getConfig(), newCurrentDir);
|
2021-02-12 14:09:28 +00:00
|
|
|
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
|
|
final Diagram system;
|
|
|
|
final ImageData imageData;
|
|
|
|
|
|
|
|
if (ssr.getBlocks().size() == 0) {
|
2021-03-04 18:11:16 +00:00
|
|
|
system = PSystemErrorUtils.buildV2(null,
|
|
|
|
new ErrorUml(SYNTAX_ERROR, "No @startuml/@enduml found", 0, new LineLocationImpl("", null)), null,
|
|
|
|
Collections.<StringLocated>emptyList());
|
2021-03-23 13:06:33 +00:00
|
|
|
imageData = ssr.noStartumlFound(os, option.getFileFormatOption());
|
2021-02-12 14:09:28 +00:00
|
|
|
} else {
|
|
|
|
system = ssr.getBlocks().get(0).getDiagram();
|
|
|
|
imageData = system.exportDiagram(os, 0, option.getFileFormatOption());
|
|
|
|
}
|
|
|
|
|
|
|
|
sendDiagram(out, system, option.getFileFormatOption(), "200", imageData, os.toByteArray());
|
|
|
|
}
|
|
|
|
|
2021-03-04 18:11:16 +00:00
|
|
|
private void sendDiagram(final BufferedOutputStream out, final Diagram system,
|
|
|
|
final FileFormatOption fileFormatOption, final String returnCode, final ImageData imageData,
|
|
|
|
final byte[] fileData) throws IOException {
|
2021-02-12 14:09:28 +00:00
|
|
|
|
|
|
|
write(out, "HTTP/1.1 " + returnCode);
|
|
|
|
write(out, "Cache-Control: no-cache");
|
|
|
|
write(out, "Server: PlantUML PicoWebServer " + Version.versionString());
|
|
|
|
write(out, "Date: " + new Date());
|
|
|
|
write(out, "Content-type: " + fileFormatOption.getFileFormat().getMimeType());
|
|
|
|
write(out, "Content-length: " + fileData.length);
|
|
|
|
write(out, "X-PlantUML-Diagram-Width: " + imageData.getWidth());
|
|
|
|
write(out, "X-PlantUML-Diagram-Height: " + imageData.getHeight());
|
|
|
|
write(out, "X-PlantUML-Diagram-Description: " + system.getDescription().getDescription());
|
|
|
|
if (system instanceof PSystemError) {
|
|
|
|
final PSystemError error = (PSystemError) system;
|
|
|
|
for (ErrorUml err : error.getErrorsUml()) {
|
|
|
|
write(out, "X-PlantUML-Diagram-Error: " + err.getError());
|
|
|
|
write(out, "X-PlantUML-Diagram-Error-Line: " + (1 + err.getLineLocation().getPosition()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
write(out, "X-Patreon: Support us on https://plantuml.com/patreon");
|
|
|
|
write(out, "X-Donate: https://plantuml.com/paypal");
|
|
|
|
write(out, "X-Quote: " + StringUtils.rot(QuoteUtils.getSomeQuote()));
|
|
|
|
write(out, "");
|
|
|
|
out.flush();
|
|
|
|
out.write(fileData);
|
|
|
|
out.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void sendError(Throwable e, BufferedOutputStream out) throws Exception {
|
|
|
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
final PrintWriter printWriter = new PrintWriter(baos);
|
|
|
|
|
|
|
|
if (e instanceof BadRequest400 && e.getCause() == null) {
|
|
|
|
printWriter.write(e.getMessage());
|
|
|
|
} else {
|
|
|
|
e.printStackTrace(printWriter);
|
|
|
|
}
|
|
|
|
printWriter.close();
|
|
|
|
|
|
|
|
write(out, "HTTP/1.1 " + (e instanceof BadRequest400 ? "400 Bad Request" : "500 Internal Server Error"));
|
|
|
|
write(out, "Content-type: text/plain");
|
|
|
|
write(out, "Content-length: " + baos.size());
|
|
|
|
write(out, "");
|
|
|
|
out.write(baos.toByteArray());
|
|
|
|
out.flush();
|
|
|
|
}
|
|
|
|
|
2020-11-21 17:33:24 +00:00
|
|
|
private String httpReturnCode(int status) {
|
|
|
|
if (status == 0 || status == 200) {
|
|
|
|
return "200 OK";
|
|
|
|
}
|
|
|
|
return "" + status + " ERROR";
|
2020-10-12 20:56:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void write(OutputStream os, String s) throws IOException {
|
|
|
|
s = s + "\r\n";
|
2021-09-13 05:18:15 +00:00
|
|
|
os.write(s.getBytes(UTF_8));
|
2020-10-12 20:56:58 +00:00
|
|
|
}
|
|
|
|
|
2021-05-08 01:07:20 +00:00
|
|
|
}
|