New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
deps: replace Jetty with HttpServer #433
Merged
elharo
merged 4 commits into
googleapis:master
from
ericraskin:replace-Jetty-with-HttpServer
Feb 20, 2020
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
cb0a581
feat: Replace Jetty with HttpServer.
ericraskin 48fb3f2
feat: Rewrite findOpenSocket using try with resources
ericraskin 84c457e
fix: Remove commented out Jetty 9.4 code since we are using HttpServ…
ericraskin 2754068
fix: Enforce Google coding style.
ericraskin File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,17 +14,24 @@ | |
|
||
package com.google.api.client.extensions.jetty.auth.oauth2; | ||
|
||
import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; | ||
import static java.net.HttpURLConnection.HTTP_OK; | ||
|
||
import com.google.api.client.extensions.java6.auth.oauth2.VerificationCodeReceiver; | ||
import com.google.api.client.util.Throwables; | ||
import com.sun.net.httpserver.Headers; | ||
import com.sun.net.httpserver.HttpContext; | ||
import com.sun.net.httpserver.HttpExchange; | ||
import com.sun.net.httpserver.HttpHandler; | ||
import com.sun.net.httpserver.HttpServer; | ||
import java.io.IOException; | ||
import java.io.OutputStream; | ||
import java.io.PrintWriter; | ||
import java.net.InetSocketAddress; | ||
import java.net.ServerSocket; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.concurrent.Semaphore; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
import org.eclipse.jetty.server.Connector; | ||
import org.eclipse.jetty.server.Request; | ||
import org.eclipse.jetty.server.Server; | ||
import org.eclipse.jetty.server.handler.AbstractHandler; | ||
|
||
/** | ||
* OAuth 2.0 verification code receiver that runs a Jetty server on a free port, waiting for a | ||
|
@@ -34,34 +41,48 @@ | |
* Implementation is thread-safe. | ||
* </p> | ||
* | ||
* @since 1.11 | ||
* @author Yaniv Inbar | ||
* @since 1.11 | ||
*/ | ||
public final class LocalServerReceiver implements VerificationCodeReceiver { | ||
|
||
private static final String LOCALHOST = "localhost"; | ||
|
||
private static final String CALLBACK_PATH = "/Callback"; | ||
|
||
/** Server or {@code null} before {@link #getRedirectUri()}. */ | ||
private Server server; | ||
/** | ||
* Server or {@code null} before {@link #getRedirectUri()}. | ||
*/ | ||
private HttpServer server; | ||
|
||
/** Verification code or {@code null} for none. */ | ||
/** | ||
* Verification code or {@code null} for none. | ||
*/ | ||
String code; | ||
|
||
/** Error code or {@code null} for none. */ | ||
/** | ||
* Error code or {@code null} for none. | ||
*/ | ||
String error; | ||
|
||
/** To block until receiving an authorization response or stop() is called. */ | ||
/** | ||
* To block until receiving an authorization response or stop() is called. | ||
*/ | ||
final Semaphore waitUnlessSignaled = new Semaphore(0 /* initially zero permit */); | ||
|
||
/** Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. */ | ||
/** | ||
* Port to use or {@code -1} to select an unused port in {@link #getRedirectUri()}. | ||
*/ | ||
private int port; | ||
|
||
/** Host name to use. */ | ||
/** | ||
* Host name to use. | ||
*/ | ||
private final String host; | ||
|
||
/** Callback path of redirect_uri */ | ||
/** | ||
* Callback path of redirect_uri. | ||
*/ | ||
private final String callbackPath; | ||
|
||
/** | ||
|
@@ -71,8 +92,8 @@ public final class LocalServerReceiver implements VerificationCodeReceiver { | |
private String successLandingPageUrl; | ||
|
||
/** | ||
* URL to an HTML page to be shown (via redirect) after failed login. If null, a canned | ||
* default landing page will be shown (via direct response). | ||
* URL to an HTML page to be shown (via redirect) after failed login. If null, a canned default | ||
* landing page will be shown (via direct response). | ||
*/ | ||
private String failureLandingPageUrl; | ||
|
||
|
@@ -94,7 +115,7 @@ public LocalServerReceiver() { | |
* @param port Port to use or {@code -1} to select an unused port | ||
*/ | ||
LocalServerReceiver(String host, int port, | ||
String successLandingPageUrl, String failureLandingPageUrl) { | ||
String successLandingPageUrl, String failureLandingPageUrl) { | ||
this(host, port, CALLBACK_PATH, successLandingPageUrl, failureLandingPageUrl); | ||
} | ||
|
||
|
@@ -105,7 +126,7 @@ public LocalServerReceiver() { | |
* @param port Port to use or {@code -1} to select an unused port | ||
*/ | ||
LocalServerReceiver(String host, int port, String callbackPath, | ||
String successLandingPageUrl, String failureLandingPageUrl) { | ||
String successLandingPageUrl, String failureLandingPageUrl) { | ||
this.host = host; | ||
this.port = port; | ||
this.callbackPath = callbackPath; | ||
|
@@ -115,28 +136,42 @@ public LocalServerReceiver() { | |
|
||
@Override | ||
public String getRedirectUri() throws IOException { | ||
server = new Server(port != -1 ? port : 0); | ||
Connector connector = server.getConnectors()[0]; | ||
connector.setHost(host); | ||
server.setHandler(new CallbackHandler()); | ||
|
||
server = HttpServer.create(new InetSocketAddress(port != -1 ? port : findOpenPort()), 0); | ||
HttpContext context = server.createContext(callbackPath, new CallbackHandler()); | ||
server.setExecutor(null); | ||
|
||
try { | ||
server.start(); | ||
port = connector.getLocalPort(); | ||
port = server.getAddress().getPort(); | ||
} catch (Exception e) { | ||
Throwables.propagateIfPossible(e); | ||
throw new IOException(e); | ||
} | ||
return "http://" + connector.getHost() + ":" + port + callbackPath; | ||
return "http://" + this.getHost() + ":" + port + callbackPath; | ||
} | ||
|
||
/* | ||
*Copied from Jetty findFreePort() as referenced by: https://gist.github.com/vorburger/3429822 | ||
*/ | ||
|
||
private int findOpenPort() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this could use try with resources |
||
try (ServerSocket socket = new ServerSocket(0)) { | ||
socket.setReuseAddress(true); | ||
return socket.getLocalPort(); | ||
} catch (IOException e) { | ||
throw new IllegalStateException("No free TCP/IP port to start embedded HTTP Server on"); | ||
} | ||
} | ||
|
||
/** | ||
* Blocks until the server receives a login result, or the server is stopped | ||
* by {@link #stop()}, to return an authorization code. | ||
* Blocks until the server receives a login result, or the server is stopped by {@link #stop()}, | ||
* to return an authorization code. | ||
* | ||
* @return authorization code if login succeeds; may return {@code null} if the server | ||
* is stopped by {@link #stop()} | ||
* @throws IOException if the server receives an error code (through an HTTP request | ||
* parameter {@code error}) | ||
* @return authorization code if login succeeds; may return {@code null} if the server is stopped | ||
* by {@link #stop()} | ||
* @throws IOException if the server receives an error code (through an HTTP request parameter | ||
* {@code error}) | ||
*/ | ||
@Override | ||
public String waitForCode() throws IOException { | ||
|
@@ -152,7 +187,7 @@ public void stop() throws IOException { | |
waitUnlessSignaled.release(); | ||
if (server != null) { | ||
try { | ||
server.stop(); | ||
server.stop(0); | ||
} catch (Exception e) { | ||
Throwables.propagateIfPossible(e); | ||
throw new IOException(e); | ||
|
@@ -161,7 +196,9 @@ public void stop() throws IOException { | |
} | ||
} | ||
|
||
/** Returns the host name to use. */ | ||
/** | ||
* Returns the host name to use. | ||
*/ | ||
public String getHost() { | ||
return host; | ||
} | ||
|
@@ -189,51 +226,69 @@ public String getCallbackPath() { | |
*/ | ||
public static final class Builder { | ||
|
||
/** Host name to use. */ | ||
/** | ||
* Host name to use. | ||
*/ | ||
private String host = LOCALHOST; | ||
|
||
/** Port to use or {@code -1} to select an unused port. */ | ||
/** | ||
* Port to use or {@code -1} to select an unused port. | ||
*/ | ||
private int port = -1; | ||
|
||
private String successLandingPageUrl; | ||
private String failureLandingPageUrl; | ||
|
||
private String callbackPath = CALLBACK_PATH; | ||
|
||
/** Builds the {@link LocalServerReceiver}. */ | ||
/** | ||
* Builds the {@link LocalServerReceiver}. | ||
*/ | ||
public LocalServerReceiver build() { | ||
return new LocalServerReceiver(host, port, callbackPath, | ||
successLandingPageUrl, failureLandingPageUrl); | ||
successLandingPageUrl, failureLandingPageUrl); | ||
} | ||
|
||
/** Returns the host name to use. */ | ||
/** | ||
* Returns the host name to use. | ||
*/ | ||
public String getHost() { | ||
return host; | ||
} | ||
|
||
/** Sets the host name to use. */ | ||
/** | ||
* Sets the host name to use. | ||
*/ | ||
public Builder setHost(String host) { | ||
this.host = host; | ||
return this; | ||
} | ||
|
||
/** Returns the port to use or {@code -1} to select an unused port. */ | ||
/** | ||
* Returns the port to use or {@code -1} to select an unused port. | ||
*/ | ||
public int getPort() { | ||
return port; | ||
} | ||
|
||
/** Sets the port to use or {@code -1} to select an unused port. */ | ||
/** | ||
* Sets the port to use or {@code -1} to select an unused port. | ||
*/ | ||
public Builder setPort(int port) { | ||
this.port = port; | ||
return this; | ||
} | ||
|
||
/** Returns the callback path of redirect_uri */ | ||
/** | ||
* Returns the callback path of redirect_uri. | ||
*/ | ||
public String getCallbackPath() { | ||
return callbackPath; | ||
} | ||
|
||
/** Set the callback path of redirect_uri */ | ||
/** | ||
* Set the callback path of redirect_uri. | ||
*/ | ||
public Builder setCallbackPath(String callbackPath) { | ||
this.callbackPath = callbackPath; | ||
return this; | ||
|
@@ -247,51 +302,73 @@ public Builder setLandingPages(String successLandingPageUrl, String failureLandi | |
} | ||
|
||
/** | ||
* Jetty handler that takes the verifier token passed over from the OAuth provider and stashes it | ||
* where {@link #waitForCode} will find it. | ||
* HttpServer handler that takes the verifier token passed over from the OAuth provider and | ||
* stashes it where {@link #waitForCode} will find it. | ||
*/ | ||
class CallbackHandler extends AbstractHandler { | ||
class CallbackHandler implements HttpHandler { | ||
|
||
@Override | ||
public void handle( | ||
String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response | ||
) | ||
throws IOException { | ||
if (!callbackPath.equals(target)) { | ||
public void handle(HttpExchange httpExchange) throws IOException { | ||
|
||
if (!callbackPath.equals(httpExchange.getRequestURI().getPath())) { | ||
return; | ||
} | ||
|
||
StringBuilder body = new StringBuilder(); | ||
|
||
try { | ||
((Request) request).setHandled(true); | ||
error = request.getParameter("error"); | ||
code = request.getParameter("code"); | ||
Map<String, String> parms = | ||
this.queryToMap(httpExchange.getRequestURI().getQuery()); | ||
error = parms.get("error"); | ||
code = parms.get("code"); | ||
|
||
Headers respHeaders = httpExchange.getResponseHeaders(); | ||
if (error == null && successLandingPageUrl != null) { | ||
response.sendRedirect(successLandingPageUrl); | ||
respHeaders.add("Location", successLandingPageUrl); | ||
httpExchange.sendResponseHeaders(HTTP_MOVED_TEMP, -1); | ||
} else if (error != null && failureLandingPageUrl != null) { | ||
response.sendRedirect(failureLandingPageUrl); | ||
respHeaders.add("Location", failureLandingPageUrl); | ||
httpExchange.sendResponseHeaders(HTTP_MOVED_TEMP, -1); | ||
} else { | ||
writeLandingHtml(response); | ||
writeLandingHtml(httpExchange, respHeaders); | ||
} | ||
response.flushBuffer(); | ||
} | ||
finally { | ||
httpExchange.close(); | ||
} finally { | ||
waitUnlessSignaled.release(); | ||
} | ||
} | ||
|
||
private void writeLandingHtml(HttpServletResponse response) throws IOException { | ||
response.setStatus(HttpServletResponse.SC_OK); | ||
response.setContentType("text/html"); | ||
private Map<String, String> queryToMap(String query) { | ||
Map<String, String> result = new HashMap<String, String>(); | ||
if (query != null) { | ||
for (String param : query.split("&")) { | ||
String pair[] = param.split("="); | ||
if (pair.length > 1) { | ||
result.put(pair[0], pair[1]); | ||
} else { | ||
result.put(pair[0], ""); | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private void writeLandingHtml(HttpExchange exchange, Headers headers) throws IOException { | ||
OutputStream os = exchange.getResponseBody(); | ||
exchange.sendResponseHeaders(HTTP_OK, 0); | ||
headers.add("ContentType", "text/html"); | ||
|
||
PrintWriter doc = response.getWriter(); | ||
PrintWriter doc = new PrintWriter(os); | ||
doc.println("<html>"); | ||
doc.println("<head><title>OAuth 2.0 Authentication Token Received</title></head>"); | ||
doc.println("<body>"); | ||
doc.println("Received verification code. You may now close this window."); | ||
doc.println("</body>"); | ||
doc.println("</html>"); | ||
doc.flush(); | ||
os.close(); | ||
} | ||
|
||
} | ||
|
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be the Javadoc comment that checkstyle is complaining about; I'm not sure. Try deleting it.