Цели высокого уровня для SPDY:
- Чтобы сократить время загрузки страницы на 50%. Наши предварительные результаты приблизились к этой цели (см. Ниже).
- Чтобы минимизировать сложность развертывания. SPDY использует TCP в качестве базового транспортного уровня, поэтому не требует никаких изменений в существующей сетевой инфраструктуре.
- Чтобы избежать необходимости каких-либо изменений в содержании со стороны авторов сайта. Единственные изменения, необходимые для поддержки SPDY, относятся к клиентскому пользовательскому агенту и приложениям веб-сервера.
- Собрать воедино единомышленников, заинтересованных в изучении протоколов как способа решения проблемы латентности. Мы надеемся разработать этот новый протокол в сотрудничестве с сообществом разработчиков программного обеспечения с открытым исходным кодом.
Некоторые конкретные технические цели:
- Чтобы разрешить много одновременных HTTP-запросов для одного сеанса TCP.
- Для уменьшения пропускной способности, используемой в настоящее время HTTP, путем сжатия заголовков и устранения ненужных заголовков.
- Определить протокол, который легко реализовать и эффективно использовать на сервере. Мы надеемся уменьшить сложность HTTP, сократив крайние случаи и определив легко анализируемые форматы сообщений.
- Чтобы сделать SSL базовым транспортным протоколом, для большей безопасности и совместимости с существующей сетевой инфраструктурой. Хотя SSL и вводит штраф за задержку, мы считаем, что долгосрочное будущее Интернета зависит от безопасного сетевого подключения. Кроме того, использование SSL необходимо, чтобы гарантировать, что связь через существующие прокси не нарушена.
- Чтобы сервер мог инициировать связь с клиентом и передавать данные клиенту, когда это возможно.
Настройка Maven
В этой статье мы не будем слишком подробно разбираться в технической реализации этого протокола, но мы покажем вам, как вы можете начать использовать и экспериментировать с SPDY самостоятельно. Для этого мы будем использовать Jetty, которая имеет реализацию SPDY, доступную в ее последней версии ( http://wiki.eclipse.org/Jetty/Feature/SPDY ).
Итак, начнем. Для этого примера мы позволим Maven обрабатывать зависимости. И мы будем использовать следующий POM.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <modelVersion>4.0.0</modelVersion> <groupId>smartjava.jetty.spdy</groupId> <artifactId>SPDY-Example</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies>  <dependency>   <groupId>org.eclipse.jetty.aggregate</groupId>   <artifactId>jetty-all-server</artifactId>   <version>8.1.2.v20120308</version>   <type>jar</type>   <scope>compile</scope>   <exclusions>    <exclusion>     <artifactId>mail</artifactId>     <groupId>javax.mail</groupId>    </exclusion>   </exclusions>  </dependency>  <dependency>   <groupId>org.eclipse.jetty.spdy</groupId>   <artifactId>spdy-jetty</artifactId>   <version>8.1.2.v20120308</version>  </dependency>  <dependency>   <groupId>org.eclipse.jetty.spdy</groupId>   <artifactId>spdy-core</artifactId>   <version>8.1.2.v20120308</version>  </dependency>  <dependency>   <groupId>org.eclipse.jetty.spdy</groupId>   <artifactId>spdy-jetty-http</artifactId>   <version>8.1.2.v20120308</version>  </dependency>  <dependency>   <groupId>org.eclipse.jetty.npn</groupId>   <artifactId>npn-api</artifactId>   <version>8.1.2.v20120308</version>                        <scope>provided</scope>  </dependency> </dependencies></project> | 
Расширение NTP TLS
  С этим POM загружаются правильные библиотеки, поэтому мы можем начать использовать определенные классы SPDY в Jetty.  Прежде чем мы действительно сможем использовать SPDY, нам также необходимо настроить Java для использования расширения протокола TLS: TLS Next Protocol Negotiation или NPN для краткости.  Подробную информацию об этом расширении можно найти в техническом примечании по Google ( http://technotes.googlecode.com/git/nextprotoneg.html ), но вкратце это сводится к этой проблеме.  Что делать, если мы хотим использовать протокол, отличный от HTTP, когда мы устанавливаем соединение с сервером через TLS.  Мы не знаем, поддерживает ли сервер этот протокол, и, поскольку SPDY сфокусирован на скорости, мы не хотим дополнительной задержки при прохождении туда-обратно.  Несмотря на то, что существует несколько различных решений, большинство из них страдают от непредсказуемости, дополнительных циклических переходов или поломок существующих прокси-серверов (для получения дополнительной информации см. Http://www.ietf.org/proceedings/80/slides/tls-1.pdf ). 
  Предложенное Google решение использует механизм расширения TLS для определения используемого протокола.  Это называется «Переговоры по следующему протоколу» или сокращенно NPN.  С этим расширением следующие шаги предпринимаются во время рукопожатия TLS: 
- Клиент показывает поддержку этого расширения
- Сервер отвечает с этой поддержкой и включает список поддерживаемых протоколов
- Клиент отправляет протокол, который он хочет использовать, который не должен быть предоставлен сервером.
Это приводит к следующему рукопожатию TLS:
ServerHello (расширение NP и список протоколов)
Сертификат *
ServerKeyExchange *
CertificateRequest *
<——– ServerHelloDone
Сертификат *
ClientKeyExchange
CertificateVerify *
[ChangeCipherSpec] NextProtocol
Закончено ——–>
[ChangeCipherSpec] <——– Завершено
Данные приложения <——-> Данные приложения
  Для получения дополнительной информации о рукопожатиях TLS / SSL посмотрите мою предыдущую статью о том, как анализировать ошибки Java SSL: http://www.smartjava.org/content/how-analyze-java-ssl-errors . 
  Поэтому нам нужен NPN, чтобы быстро определить протокол, который мы хотим использовать.  Поскольку это не стандартный TLS, нам нужно настроить Java для использования NPN.  Стандартная Java (пока) не поддерживает NPN, поэтому мы не можем запустить SPDY на стандартной JVM.  Чтобы решить эту проблему, Jetty создала реализацию NPN, которую можно использовать вместе с OpenJDK 7 (для получения более подробной информации см. Http://wiki.eclipse.org/Jetty/Feature/NPN ).  Вы можете скачать эту реализацию здесь: http://repo2.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/, и вы должны добавить ее в путь загрузки вашего класса как таковой: 
| 1 | java -Xbootclasspath/p:<path_to_npn_boot_jar> ... | 
Завершение HTTP-запроса в SPDY
Теперь вы можете начать использовать SPDY от Jetty. Jetty поддерживает эту функцию двумя различными способами. Вы можете использовать его для прозрачного преобразования из SPDY в HTTP и обратно или использовать его для непосредственного общения с SPDY. Давайте создадим простую конфигурацию сервера, на которой размещается некоторый статический контент, используя соединение с поддержкой SPDY. Для этого мы будем использовать следующую конфигурацию Jetty:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | importorg.eclipse.jetty.server.Connector;importorg.eclipse.jetty.server.Server;importorg.eclipse.jetty.server.handler.ContextHandler;importorg.eclipse.jetty.server.handler.ResourceHandler;importorg.eclipse.jetty.spdy.http.HTTPSPDYServerConnector;importorg.eclipse.jetty.util.ssl.SslContextFactory;  publicclassSPDYServerLauncher {  publicstaticvoidmain(String[] args) throwsException {   // the server to start  Server server = newServer();   // the ssl context to use  SslContextFactory sslFactory = newSslContextFactory();  sslFactory.setKeyStorePath("src/main/resources/spdy.keystore");  sslFactory.setKeyStorePassword("secret");  sslFactory.setProtocol("TLSv1");   // simple connector to add to serve content using spdy  Connector connector = newHTTPSPDYServerConnector(sslFactory);  connector.setPort(8443);   // add connector to the server  server.addConnector(connector);   // add a handler to serve content  ContextHandler handler = newContextHandler();  handler.setContextPath("/content");  handler.setResourceBase("src/main/resources/webcontent");  handler.setHandler(newResourceHandler());   server.setHandler(handler);   server.start();  server.join(); }} | 
Поскольку Jetty также имеет очень гибкий язык конфигурации XML, вы можете сделать то же самое, используя следующую конфигурацию XML.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <Configureid="Server"class="org.eclipse.jetty.server.Server">     <Newid="sslContextFactory"class="org.eclipse.jetty.util.ssl.SslContextFactory">        <Setname="keyStorePath">src/main/resources/spdy.keystore</Set>        <Setname="keyStorePassword">secret</Set>        <Setname="protocol">TLSv1</Set>    </New>     <Callname="addConnector">        <Arg>            <Newclass="org.eclipse.jetty.spdy.http.HTTPSPDYServerConnector">                <Arg>                    <Refid="sslContextFactory"/>                </Arg>                <Setname="Port">8443</Set>            </New>        </Arg>    </Call>    // Use standard XML configuration for the other handlers and other  // stuff you want to add </Configure> | 
Как вы можете видеть в этом списке, мы указываем контекст SSL. Это необходимо, поскольку SPDY работает над TLS. Когда мы запустим эту конфигурацию, Jetty начнет прослушивать порт 8443 для соединений SPDY. Не все браузеры пока поддерживают SPDY, я протестировал этот пример с использованием новейшего браузера Chrome. Если вы перейдете по адресу https: // localhost: 8443 / dummy.html (файл, который я создал для тестирования), вы увидите содержимое этого файла, так же, как вы запрашивали его с помощью HTTPS. Так что здесь происходит? Давайте сначала посмотрим на представление сеанса SPDY, которое предоставляет Chrome, чтобы определить, действительно ли мы используем SPDY. Если вы переходите по следующему URL: chrome: // net-internals / # events & q = type: SPDY_SESSION% 20is: active. Вы увидите что-то вроде следующего рисунка.
В этом представлении вы можете увидеть все текущие сеансы SPDY. Если все настроено правильно, вы также можете увидеть сеанс SPDY, подключенный к localhost. Дополнительная проверка, чтобы увидеть, все ли работает как задумано, чтобы включить отладку расширения NPN. Вы можете сделать это, добавив следующую строку в код Java, который вы используете для запуска сервера:
Используйте протокол SPDY напрямую
Теперь, когда у нас работает HTTP через SPDY, давайте рассмотрим другой вариант, который предоставляет Jetty, который позволяет нам напрямую отправлять и получать сообщения SPDY. Для этого примера мы просто создадим клиент, который отправляет сообщение на сервер каждые 5 секунд. Сервер отправляет ответы подключенному клиенту с количеством полученных сообщений каждую секунду. Сначала мы создаем код сервера.
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | importjava.util.concurrent.Executors;importjava.util.concurrent.ScheduledExecutorService;importjava.util.concurrent.TimeUnit; importorg.eclipse.jetty.server.Server;importorg.eclipse.jetty.spdy.SPDYServerConnector;importorg.eclipse.jetty.spdy.api.DataInfo;importorg.eclipse.jetty.spdy.api.ReplyInfo;importorg.eclipse.jetty.spdy.api.Stream;importorg.eclipse.jetty.spdy.api.StreamFrameListener;importorg.eclipse.jetty.spdy.api.StringDataInfo;importorg.eclipse.jetty.spdy.api.SynInfo;importorg.eclipse.jetty.spdy.api.server.ServerSessionFrameListener; publicclassSPDYListener {  publicstaticvoidmain(String[] args) throwsException {   // Frame listener that handles the communication over speedy    ServerSessionFrameListener frameListener = newServerSessionFrameListener.Adapter() {    /**    * As soon as we receive a syninfo we return the handler for the stream on     * this session    */   @Override   publicStreamFrameListener onSyn(finalStream stream, SynInfo synInfo) {     // Send a reply to this message    stream.reply(newReplyInfo(false));     // and start a timer that sends a request to this stream every 5 seconds    ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();    Runnable periodicTask = newRunnable() {      privateinti = 0;         publicvoidrun() {          // send a request and don't close the stream             stream.data(newStringDataInfo("Data from the server "+ i++, false));         }     };    executor.scheduleAtFixedRate(periodicTask, 0, 1, TimeUnit.SECONDS);     // Next create an adapter to further handle the client input from specific stream.    returnnewStreamFrameListener.Adapter() {      /**      * We're only interested in the data, not the headers in this      * example      */     publicvoidonData(Stream stream, DataInfo dataInfo) {      String clientData = dataInfo.asString("UTF-8", true);      System.out.println("Received the following client data: "+ clientData);     }    };   }  };   // Wire up and start the connector  org.eclipse.jetty.server.Server server = newServer();  SPDYServerConnector connector = newSPDYServerConnector(frameListener);  connector.setPort(8181);   server.addConnector(connector);  server.start();  server.join(); }} | 
И код клиента выглядит так:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | importjava.net.InetSocketAddress;importjava.util.concurrent.Executors;importjava.util.concurrent.ScheduledExecutorService;importjava.util.concurrent.TimeUnit; importorg.eclipse.jetty.spdy.SPDYClient;importorg.eclipse.jetty.spdy.api.DataInfo;importorg.eclipse.jetty.spdy.api.SPDY;importorg.eclipse.jetty.spdy.api.Session;importorg.eclipse.jetty.spdy.api.Stream;importorg.eclipse.jetty.spdy.api.StreamFrameListener;importorg.eclipse.jetty.spdy.api.StringDataInfo;importorg.eclipse.jetty.spdy.api.SynInfo; /** * Calls the server every couple of seconds. *  * @author jos */publicclassSPDYCaller {  publicstaticvoidmain(String[] args) throwsException {   // this listener receives data from the server. It then prints out the data  StreamFrameListener streamListener = newStreamFrameListener.Adapter() {       publicvoidonData(Stream stream, DataInfo dataInfo)  {          // Data received from server          String content = dataInfo.asString("UTF-8", true);          System.out.println("SPDY content: "+ content);      }  };   // Create client  SPDYClient.Factory clientFactory = newSPDYClient.Factory();  clientFactory.start();  SPDYClient client = clientFactory.newSPDYClient(SPDY.V2);   // Create a session to the server running on localhost port 8181  Session session = client.connect(newInetSocketAddress("localhost", 8181), null).get(5, TimeUnit.SECONDS);   // Start a new session, and configure the stream listener  finalStream stream = session.syn(newSynInfo(false), streamListener).get(5, TimeUnit.SECONDS);   //start a timer that sends a request to this stream every second  ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();  Runnable periodicTask = newRunnable() {    privateinti = 0;        publicvoidrun() {        // send a request, don't close the stream        stream.data(newStringDataInfo("Data from the client "+ i++, false));       }   };  executor.scheduleAtFixedRate(periodicTask, 0, 1, TimeUnit.SECONDS); }} | 
Это показывает следующий вывод на клиенте и на сервере:
..
SPDY контент: данные с сервера 3
SPDY контент: данные с сервера 4
SPDY контент: данные с сервера 5
SPDY контент: данные с сервера 6
.. сервер:
…
Получены следующие данные клиента: Данные от клиента 2
Получены следующие данные клиента: Данные от клиента 3
Получены следующие данные клиента: Данные от клиента 4
Получены следующие данные клиента: Данные от клиента 5
…
Сам код должен быть понятен из встроенных комментариев. Единственное, что нужно помнить, когда вы хотите отправить более одного сообщения данных через поток, это убедиться, что для второго параметра конструктора StringDataInfo установлено значение false. Если установлено значение true, поток будет закрыт после отправки данных.
| 1 | stream.data(newStringDataInfo("Data from the client "+ i++, false)); | 
Это просто показывает простой пример использования протокола SPDY напрямую. Дополнительную информацию и примеры можно найти на вики Jetty и в документации SPDY API .
Ссылка: Как использовать SPDY с Jetty от нашего партнера JCG Йоса Дирксена в блоге Smart Java .
