Grizzly Http Server - принимает только одно соединение за раз

У меня есть сервер Grizzly Http с добавленной асинхронной обработкой. Он ставит в очередь мои запросы и обрабатывает только один запрос за раз, несмотря на добавление к нему поддержки асинхронности.

Путь HttpHandler был привязан к: "/" Номер порта: 7777

Когда я одновременно нажимаю http://localhost:7777 из двух браузеров, наблюдается следующее поведение: второй вызов ожидает завершения первого. Я хочу, чтобы мой второй http-вызов также работал одновременно с первым http-вызовом.

EDIT ссылка на Github мой проект

Вот классы
GrizzlyMain.java

package com.grizzly;

import java.io.IOException;
import java.net.URI;

import javax.ws.rs.core.UriBuilder;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.strategies.WorkerThreadIOStrategy;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;

import com.grizzly.http.IHttpHandler;
import com.grizzly.http.IHttpServerFactory;

public class GrizzlyMain {

    private static HttpServer httpServer;

    private static void startHttpServer(int port) throws IOException {
        URI uri = getBaseURI(port);

        httpServer = IHttpServerFactory.createHttpServer(uri,
            new IHttpHandler(null));

        TCPNIOTransport transport = getListener(httpServer).getTransport();

        ThreadPoolConfig config = ThreadPoolConfig.defaultConfig()
                .setPoolName("worker-thread-").setCorePoolSize(6).setMaxPoolSize(6)
                .setQueueLimit(-1)/* same as default */;

        transport.configureBlocking(false);
        transport.setSelectorRunnersCount(3);
        transport.setWorkerThreadPoolConfig(config);
        transport.setIOStrategy(WorkerThreadIOStrategy.getInstance());
        transport.setTcpNoDelay(true);

        System.out.println("Blocking Transport(T/F): " + transport.isBlocking());
        System.out.println("Num SelectorRunners: "
            + transport.getSelectorRunnersCount());
        System.out.println("Num WorkerThreads: "
            + transport.getWorkerThreadPoolConfig().getCorePoolSize());

        httpServer.start();
        System.out.println("Server Started @" + uri.toString());
    }

    public static void main(String[] args) throws InterruptedException,
        IOException, InstantiationException, IllegalAccessException,
        ClassNotFoundException {
        startHttpServer(7777);

        System.out.println("Press any key to stop the server...");
        System.in.read();
    }

    private static NetworkListener getListener(HttpServer httpServer) {
        return httpServer.getListeners().iterator().next();
    }

    private static URI getBaseURI(int port) {
        return UriBuilder.fromUri("https://0.0.0.0/").port(port).build();
    }

}

HttpHandler (со встроенной поддержкой асинхронности)

package com.grizzly.http;

import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ExecutorService;

import javax.ws.rs.core.Application;

import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.Request;
import org.glassfish.grizzly.http.server.Response;
import org.glassfish.grizzly.http.util.HttpStatus;
import org.glassfish.grizzly.threadpool.GrizzlyExecutorService;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.glassfish.jersey.server.ApplicationHandler;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.Container;

import com.grizzly.Utils;

/**
 * Jersey {@code Container} implementation based on Grizzly
 * {@link org.glassfish.grizzly.http.server.HttpHandler}.
 *
 * @author Jakub Podlesak (jakub.podlesak at oracle.com)
 * @author Libor Kramolis (libor.kramolis at oracle.com)
 * @author Marek Potociar (marek.potociar at oracle.com)
 */
public final class IHttpHandler extends HttpHandler implements Container {

    private static int reqNum = 0;

    final ExecutorService executorService = GrizzlyExecutorService
            .createInstance(ThreadPoolConfig.defaultConfig().copy()
                    .setCorePoolSize(4).setMaxPoolSize(4));

    private volatile ApplicationHandler appHandler;

    /**
     * Create a new Grizzly HTTP container.
     *
     * @param application
     *          JAX-RS / Jersey application to be deployed on Grizzly HTTP
     *          container.
     */
    public IHttpHandler(final Application application) {
    }

    @Override
    public void start() {
        super.start();
    }

    @Override
    public void service(final Request request, final Response response) {
        System.out.println("\nREQ_ID: " + reqNum++);
        System.out.println("THREAD_ID: " + Utils.getThreadName());

        response.suspend();
        // Instruct Grizzly to not flush response, once we exit service(...) method

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("Executor Service Current THREAD_ID: "
                            + Utils.getThreadName());
                    Thread.sleep(25 * 1000);
                } catch (Exception e) {
                    response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
                } finally {
                    String content = updateResponse(response);
                    System.out.println("Response resumed > " + content);
                    response.resume();
                }
            }
        });
    }

    @Override
    public ApplicationHandler getApplicationHandler() {
        return appHandler;
    }

    @Override
    public void destroy() {
        super.destroy();
        appHandler = null;
    }

    // Auto-generated stuff
    @Override
    public ResourceConfig getConfiguration() {
        return null;
    }

    @Override
    public void reload() {

    }

    @Override
    public void reload(ResourceConfig configuration) {
    }

    private String updateResponse(final Response response) {
        String data = null;
        try {
            data = new Date().toLocaleString();
            response.getWriter().write(data);
        } catch (IOException e) {
            data = "Unknown error from our server";
            response.setStatus(500, data);
        }

        return data;
    }

}

IHttpServerFactory.java

package com.grizzly.http;

import java.net.URI;

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.ServerConfiguration;

/**
 * @author smc
 */
public class IHttpServerFactory {

    private static final int DEFAULT_HTTP_PORT = 80;

    public static HttpServer createHttpServer(URI uri, IHttpHandler handler) {

        final String host = uri.getHost() == null ? NetworkListener.DEFAULT_NETWORK_HOST
            : uri.getHost();
        final int port = uri.getPort() == -1 ? DEFAULT_HTTP_PORT : uri.getPort();

        final NetworkListener listener = new NetworkListener("IGrizzly", host, port);
        listener.setSecure(false);

        final HttpServer server = new HttpServer();
        server.addListener(listener);

        final ServerConfiguration config = server.getServerConfiguration();
        if (handler != null) {
            config.addHttpHandler(handler, uri.getPath());
        }

        config.setPassTraceRequest(true);
        return server;
    }
}

person smc    schedule 05.08.2014    source источник
comment
Можете ли вы сказать, что блокирует? Если вы поставите точку останова в начале service, каково наблюдаемое поведение? Точно так же, что произойдет, если вы остановитесь на первой строке run?   -  person Raffaele    schedule 05.08.2014
comment
@Raffaele Когда я вставляю точку останова в начале службы, поток останавливается на этом. Если я сделаю еще один http-вызов (фактически любое количество http-вызовов) с другой вкладки, с выполнением ничего не произойдет. Только после того, как я перешагну и продолжу выполнение и, в конце концов, завершу этот вызов, один из ожидающих HTTP-вызовов войдет в метод обслуживания. Такое же поведение даже после того, как я сломал первую строку run(). Никакие другие http-вызовы не выполняются параллельно (в некоторых других рабочих потоках).   -  person smc    schedule 05.08.2014
comment
Таким образом, неправильная конфигурация, по-видимому, связана со частью Grizzly, как если бы она использовала только один поток для обслуживания всех входящих соединений (в качестве двойной проверки вы всегда должны видеть один и тот же идентификатор потока в своем стандартном выводе). Попробуйте удалить всю свою пользовательскую конфигурацию в начале GrizzlyMain, просто чтобы посмотреть, работают ли заводские настройки по-другому.   -  person Raffaele    schedule 05.08.2014
comment
@Raffaele Не повезло! То же поведение; Обработчик http, который выбрал запрос от сетевого прослушивателя (порт № 7777), по-видимому, заблокирован в ожидании завершения рабочего потока.   -  person smc    schedule 05.08.2014
comment
Какую версию Grizzly вы используете?   -  person shlomi33    schedule 05.08.2014
comment
@shlomi33 Добавлена ​​ссылка на github в описании вопроса — github.com/sehumadhav/grizzly/blob/master/grizzly/src/com/   -  person smc    schedule 05.08.2014
comment
@Raffaele Добавлена ​​ссылка на github в описании вопроса — github.com/sehumadhav/grizzly/blob/master/grizzly/src/com/   -  person smc    schedule 05.08.2014
comment
Просто чтобы вы знали: кажется, что блокировка для каждого браузера. Не могли бы вы проверить с двумя разными браузерами?   -  person Raffaele    schedule 06.08.2014


Ответы (1)


Кажется, проблема заключается в том, что браузер ожидает завершения первого запроса, и, следовательно, проблема больше на стороне клиента, чем на стороне сервера. Он исчезает, если вы тестируете два разных процесса браузера или даже если вы открываете два разных пути (скажем, localhost:7777/foo и localhost:7777/bar) в одном и том же процессе браузера (примечание: строка запроса участвует в создании пути в строке HTTP-запроса).

Как я это понял

Соединения в HTTP/1.1 по умолчанию являются постоянными, т. е. браузеры повторно используют одно и то же TCP-соединение снова и снова, чтобы ускорить процесс. Однако это не означает, что все запросы к одному и тому же домену будут сериализованы: фактически пул соединений выделяется для каждого имени хоста (источник). К сожалению, запросы с одним и тем же путем эффективно ставятся в очередь (по крайней мере, в Firefox и Chrome) - я думаю, это устройство, которое браузеры используют для защиты ресурсов сервера (и, следовательно, взаимодействия с пользователем).

Реальные приложения не страдают от этого, потому что разные ресурсы развертываются по разным URL-адресам.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я написал этот ответ, основываясь на своих наблюдениях и некоторых обоснованных предположениях. Я думаю, что на самом деле все может быть так, однако следует использовать такой инструмент, как Wireshark, чтобы отслеживать поток TCP и определенно утверждать, что это то, что происходит.

person Raffaele    schedule 05.08.2014