
com.dukescript.presenters.Browser Maven / Gradle / Ivy
package com.dukescript.presenters;
/*
* #%L
* DukeScript Presenter for any Browser - a library from the "DukeScript Presenters" project.
* Visit http://dukescript.com for support and commercial license.
* %%
* Copyright (C) 2015 Eppleton IT Consulting
* %%
* This program 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.
*
* This program is 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 program. If not, see
* .
* #L%
*/
import com.dukescript.presenters.renderer.Show;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.Flushable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.glassfish.grizzly.PortRange;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.server.ServerConfiguration;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.netbeans.html.boot.spi.Fn;
import org.netbeans.html.boot.spi.Fn.Presenter;
import org.openide.util.lookup.ServiceProvider;
/** Browser based {@link Presenter}. It starts local server and
* launches browser that connects to it. The actual browser to
* be launched can be influenced by value of
* com.dukescript.presenters.browser
property.
* It can have following values:
*
* - GTK - use Gtk WebKit implementation. Requires presence of
* appropriate native libraries
* - AWT - use {@link java.awt.Desktop#browse(java.net.URI)} to
* launch a browser
* - NONE - just launches the server, useful together with
*
com.dukescript.presenters.browserPort
property that
* can specify a fixed port to open the server at
*
* - any other value is interpreted as a command which is then
* launched on a command line with one parameter - the URL to connect to
*
* If the property is not specified the system tries GTK mode first,
* followed by AWT and then tries to execute xdg-open
* (default LINUX command to launch a browser from a shell script).
*
* To use this presenter specify following dependency:
*
* <dependency>
* <groupId>com.dukescript.presenters</groupId>
* <artifactId>browser</artifactId>
* <version>1.x</version>
* </dependency>
*
*/
@ServiceProvider(service = Presenter.class)
public final class Browser implements Fn.Presenter, Fn.KeepAlive, Flushable, Executor {
static final Logger LOG = Logger.getLogger(Browser.class.getName());
private final Map SESSIONS = new HashMap();
private final String app;
private HttpServer s;
private Runnable onPageLoad;
private Command current;
public Browser() throws Exception {
this(findCalleeClassName());
}
Browser(String app) throws Exception {
this.app = app;
}
@Override
public final void execute(final Runnable r) {
current.runSafe(r, true);
}
HttpServer server() {
return s;
}
static HttpServer findServer(Object obj) {
return ((Command)obj).browser.server();
}
/** Shows URL in a browser.
* @param page the page to display in the browser
* @throws IOException if something goes wrong
*/
void show(URI page) throws IOException {
String impl = System.getProperty("com.dukescript.presenters.browser"); // NOI18N
if ("none".equalsIgnoreCase(impl)) { // NOI18N
return;
}
if (impl != null) {
Show.show(impl, page);
} else {
IOException one, two = null;
try {
String ui = System.getProperty("os.name").contains("Mac") ?
"Cocoa" : "GTK";
Show.show(ui, page);
return;
} catch (IOException ex) {
one = ex;
}
try {
Show.show("AWT", page);
return;
} catch (IOException ex) {
two = ex;
}
try {
Show.show(impl, page);
return;
} catch (IOException ex) {
two.initCause(one);
ex.initCause(two);
throw ex;
}
}
}
@Override
public Fn defineFn(String string, String... strings) {
throw new UnsupportedOperationException();
}
@Override
public void loadScript(Reader reader) throws Exception {
throw new UnsupportedOperationException();
}
@Override
public Fn defineFn(String string, String[] strings, boolean[] blns) {
throw new UnsupportedOperationException();
}
@Override
public void flush() throws IOException {
throw new UnsupportedOperationException();
}
private static HttpServer server(RootPage r) {
int from = 8080;
int to = 65535;
String port = System.getProperty("com.dukescript.presenters.browserPort"); // NOI18N
if (port != null) {
from = to = Integer.parseInt(port);
}
HttpServer s = HttpServer.createSimpleServer(null, new PortRange(from, to));
final ServerConfiguration conf = s.getServerConfiguration();
conf.addHttpHandler(r, "/");
return s;
}
private static URI pageURL(String protocol, HttpServer server, final String page) {
NetworkListener listener = server.getListeners().iterator().next();
int port = listener.getPort();
try {
return new URI(protocol + "://localhost:" + port + page);
} catch (URISyntaxException ex) {
throw new IllegalStateException(ex);
}
}
@Override
public final void displayPage(URL page, Runnable onPageLoad) {
try {
this.onPageLoad = onPageLoad;
s = server(new RootPage(page));
s.start();
show(pageURL("http", s, "/"));
} catch (IOException ex) {
Logger.getLogger(Browser.class.getName()).log(Level.SEVERE, null, ex);
}
}
private final class RootPage extends HttpHandler {
private final URL page;
public RootPage(URL page) {
this.page = page;
}
@Override
public void service(Request rqst, Response rspns) throws Exception {
String path = rqst.getRequestURI();
rspns.setCharacterEncoding("UTF-8");
if ("/".equals(path) || "index.html".equals(path)) {
Reader is;
Writer w = rspns.getWriter();
rspns.setContentType("text/html");
final Command cmd = new Command(Browser.this);
try {
is = new InputStreamReader(page.openStream());
} catch (IOException ex) {
w.write("");
w.write("Browser
");
w.write("");
emitScript(w, cmd.id);
w.write("");
w.close();
return;
}
SESSIONS.put(cmd.id, cmd);
int state = 0;
for (;;) {
int ch = is.read();
if (ch == -1) {
break;
}
char lower = Character.toLowerCase((char)ch);
switch (state) {
case 1000: break;
case 0: if (lower == '<') state = 1; break;
case 1: if (lower == 'b') state = 2;
else if (lower != ' ' && lower != '\n') state = 0;
break;
case 2: if (lower == 'o') state = 3; else state = 0; break;
case 3: if (lower == 'd') state = 4; else state = 0; break;
case 4: if (lower == 'y') state = 5; else state = 0; break;
case 5: if (lower == '>') state = 500;
else if (lower != ' ' && lower != '\n') state = 0;
break;
}
w.write((char)ch);
if (state == 500) {
emitScript(w, cmd.id);
state = 1000;
}
}
if (state != 1000) {
emitScript(w, cmd.id);
}
is.close();
w.close();
} else if (path.equals("/command.js")) {
String id = rqst.getParameter("id");
Command c = SESSIONS.get(id);
if (c == null) {
throw new NullPointerException("No command for " + id);
}
c.service(rqst, rspns);
} else {
if (path.startsWith("/")) {
path = path.substring(1);
}
URL relative = new URL(page, path);
InputStream is;
try {
is = relative.openStream();
} catch (FileNotFoundException ex) {
rspns.setStatus(HttpStatus.NOT_FOUND_404);
return;
}
OutputStream out = rspns.getOutputStream();
for (;;) {
int b = is.read();
if (b == -1) {
break;
}
out.write(b);
}
out.close();
is.close();
}
}
private void emitScript(Writer w, String id) throws IOException {
w.write(" \n");
}
}
private static String findCalleeClassName() {
StackTraceElement[] frames = new Exception().getStackTrace();
for (StackTraceElement e : frames) {
String cn = e.getClassName();
if (cn.startsWith("com.dukescript.presenters.")) { // NOI18N
continue;
}
if (cn.startsWith("org.netbeans.html.")) { // NOI18N
continue;
}
if (cn.startsWith("net.java.html.")) { // NOI18N
continue;
}
if (cn.startsWith("java.")) { // NOI18N
continue;
}
if (cn.startsWith("javafx.")) { // NOI18N
continue;
}
if (cn.startsWith("com.sun.")) { // NOI18N
continue;
}
return cn;
}
return "org.netbeans.html"; // NOI18N
}
private static final class Command extends Generic
implements Fn.Presenter, Fn.KeepAlive, Flushable, Executor, ThreadFactory {
private final Queue
© 2015 - 2025 Weber Informatics LLC | Privacy Policy