// Copyright 2016 The Domain Registry Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package google.registry.server; import static com.google.common.base.Preconditions.checkArgument; import static google.registry.util.NetworkUtils.getCanonicalHostName; import com.google.common.base.Throwables; import com.google.common.net.HostAndPort; import com.google.common.util.concurrent.Callables; import com.google.common.util.concurrent.SimpleTimeLimiter; import org.mortbay.jetty.Connector; import org.mortbay.jetty.Server; import org.mortbay.jetty.bio.SocketConnector; import org.mortbay.jetty.servlet.Context; import org.mortbay.jetty.servlet.DefaultServlet; import org.mortbay.jetty.servlet.ServletHolder; import java.net.MalformedURLException; import java.net.URL; import java.nio.file.Path; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.servlet.http.HttpServlet; /** * HTTP server that serves static content and handles servlet requests in the calling thread. * *
Using this server is similar to to other server classes, in that it has {@link #start()} and * {@link #stop()} methods. However a {@link #process()} method was added, which is used to process * requests made to servlets (not static files) in the calling thread. * *
Note: This server is intended for development purposes. For the love all that is good, * do not make this public facing. * *
Jetty6 is multithreaded and provides no mechanism for controlling which threads execute your * requests. HttpServer solves this problem by wrapping all the servlets provided to the constructor * inside {@link ServletWrapperDelegatorServlet}. When requests come in, a {@link FutureTask} will * be sent back to this class using a {@link LinkedBlockingDeque} message queue. Those messages are * then consumed by the {@code process()} method. * *
The reason why this is necessary is because the App Engine local testing services (created by
* {@code LocalServiceTestHelper}) only apply to a single thread (probably to allow multi-threaded
* tests). So when Jetty creates random threads to handle requests, they won't have access to the
* datastore and other stuff.
*/
public final class TestServer {
private static final int DEFAULT_PORT = 80;
private static final String CONTEXT_PATH = "/";
private static final int STARTUP_TIMEOUT_MS = 5000;
private static final int SHUTDOWN_TIMEOUT_MS = 5000;
private final HostAndPort urlAddress;
private final Server server = new Server();
private final BlockingQueue This method should be called from within a loop.
*
* @throws InterruptedException if this thread was interrupted while waiting for a request.
*/
public void process() throws InterruptedException {
requestQueue.take().run();
}
/**
* Adds a fake entry to this server's event loop.
*
* This is useful in situations when a random thread wants {@link #process()} to return in the
* main event loop, for post-request processing.
*/
public void ping() {
requestQueue.add(new FutureTask<>(Callables.