Home | History | Annotate | Download | only in page
      1 /*
      2  * Copyright (C) 2009 Ericsson AB
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer
     13  *    in the documentation and/or other materials provided with the
     14  *    distribution.
     15  * 3. Neither the name of Ericsson nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this
     17  *    software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #include "config.h"
     33 
     34 #if ENABLE(EVENTSOURCE)
     35 
     36 #include "EventSource.h"
     37 
     38 #include "Cache.h"
     39 #include "DOMWindow.h"
     40 #include "Event.h"
     41 #include "EventException.h"
     42 #include "PlatformString.h"
     43 #include "MessageEvent.h"
     44 #include "ResourceError.h"
     45 #include "ResourceRequest.h"
     46 #include "ResourceResponse.h"
     47 #include "ScriptExecutionContext.h"
     48 #include "SerializedScriptValue.h"
     49 #include "TextResourceDecoder.h"
     50 #include "ThreadableLoader.h"
     51 
     52 namespace WebCore {
     53 
     54 const unsigned long long EventSource::defaultReconnectDelay = 3000;
     55 
     56 EventSource::EventSource(const String& url, ScriptExecutionContext* context, ExceptionCode& ec)
     57     : ActiveDOMObject(context, this)
     58     , m_state(CONNECTING)
     59     , m_reconnectTimer(this, &EventSource::reconnectTimerFired)
     60     , m_discardTrailingNewline(false)
     61     , m_failSilently(false)
     62     , m_requestInFlight(false)
     63     , m_reconnectDelay(defaultReconnectDelay)
     64 {
     65     if (url.isEmpty() || !(m_url = context->completeURL(url)).isValid()) {
     66         ec = SYNTAX_ERR;
     67         return;
     68     }
     69     // FIXME: should support cross-origin requests
     70     if (!scriptExecutionContext()->securityOrigin()->canRequest(m_url)) {
     71         ec = SECURITY_ERR;
     72         return;
     73     }
     74 
     75     m_origin = scriptExecutionContext()->securityOrigin()->toString();
     76     m_decoder = TextResourceDecoder::create("text/plain", "UTF-8");
     77 
     78     setPendingActivity(this);
     79     connect();
     80 }
     81 
     82 EventSource::~EventSource()
     83 {
     84 }
     85 
     86 void EventSource::connect()
     87 {
     88     ResourceRequest request(m_url);
     89     request.setHTTPMethod("GET");
     90     request.setHTTPHeaderField("Accept", "text/event-stream");
     91     request.setHTTPHeaderField("Cache-Control", "no-cache");
     92     if (!m_lastEventId.isEmpty())
     93         request.setHTTPHeaderField("Last-Event-ID", m_lastEventId);
     94 
     95     ThreadableLoaderOptions options;
     96     options.sendLoadCallbacks = true;
     97     options.sniffContent = false;
     98     options.allowCredentials = true;
     99 
    100     m_loader = ThreadableLoader::create(scriptExecutionContext(), this, request, options);
    101 
    102     m_requestInFlight = true;
    103 
    104     if (!scriptExecutionContext()->isWorkerContext())
    105         cache()->loader()->nonCacheRequestInFlight(m_url);
    106 }
    107 
    108 void EventSource::endRequest()
    109 {
    110     m_requestInFlight = false;
    111 
    112     if (!m_failSilently)
    113         dispatchEvent(Event::create(eventNames().errorEvent, false, false));
    114 
    115     if (!scriptExecutionContext()->isWorkerContext())
    116         cache()->loader()->nonCacheRequestComplete(m_url);
    117 
    118     if (m_state != CLOSED)
    119         scheduleReconnect();
    120     else
    121         unsetPendingActivity(this);
    122 }
    123 
    124 void EventSource::scheduleReconnect()
    125 {
    126     m_state = CONNECTING;
    127     m_reconnectTimer.startOneShot(m_reconnectDelay / 1000);
    128 }
    129 
    130 void EventSource::reconnectTimerFired(Timer<EventSource>*)
    131 {
    132     connect();
    133 }
    134 
    135 String EventSource::url() const
    136 {
    137     return m_url.string();
    138 }
    139 
    140 EventSource::State EventSource::readyState() const
    141 {
    142     return m_state;
    143 }
    144 
    145 void EventSource::close()
    146 {
    147     if (m_state == CLOSED)
    148         return;
    149 
    150     if (m_reconnectTimer.isActive()) {
    151         m_reconnectTimer.stop();
    152         unsetPendingActivity(this);
    153     }
    154 
    155     m_state = CLOSED;
    156     m_failSilently = true;
    157 
    158     if (m_requestInFlight)
    159         m_loader->cancel();
    160 }
    161 
    162 ScriptExecutionContext* EventSource::scriptExecutionContext() const
    163 {
    164     return ActiveDOMObject::scriptExecutionContext();
    165 }
    166 
    167 void EventSource::didReceiveResponse(const ResourceResponse& response)
    168 {
    169     int statusCode = response.httpStatusCode();
    170     if (statusCode == 200 && response.httpHeaderField("Content-Type") == "text/event-stream") {
    171         m_state = OPEN;
    172         dispatchEvent(Event::create(eventNames().openEvent, false, false));
    173     } else {
    174         if (statusCode <= 200 || statusCode > 299)
    175             m_state = CLOSED;
    176         m_loader->cancel();
    177     }
    178 }
    179 
    180 void EventSource::didReceiveData(const char* data, int length)
    181 {
    182     append(m_receiveBuf, m_decoder->decode(data, length));
    183     parseEventStream();
    184 }
    185 
    186 void EventSource::didFinishLoading(unsigned long)
    187 {
    188     if (m_receiveBuf.size() > 0 || m_data.size() > 0) {
    189         append(m_receiveBuf, "\n\n");
    190         parseEventStream();
    191     }
    192     m_state = CONNECTING;
    193     endRequest();
    194 }
    195 
    196 void EventSource::didFail(const ResourceError& error)
    197 {
    198     int canceled = error.isCancellation();
    199     if (((m_state == CONNECTING) && !canceled) || ((m_state == OPEN) && canceled))
    200         m_state = CLOSED;
    201     endRequest();
    202 }
    203 
    204 void EventSource::didFailRedirectCheck()
    205 {
    206     m_state = CLOSED;
    207     m_loader->cancel();
    208 }
    209 
    210 void EventSource::parseEventStream()
    211 {
    212     unsigned int bufPos = 0;
    213     unsigned int bufSize = m_receiveBuf.size();
    214     while (bufPos < bufSize) {
    215         if (m_discardTrailingNewline) {
    216             if (m_receiveBuf[bufPos] == '\n')
    217                 bufPos++;
    218             m_discardTrailingNewline = false;
    219         }
    220 
    221         int lineLength = -1;
    222         int fieldLength = -1;
    223         for (unsigned int i = bufPos; lineLength < 0 && i < bufSize; i++) {
    224             switch (m_receiveBuf[i]) {
    225             case ':':
    226                 if (fieldLength < 0)
    227                     fieldLength = i - bufPos;
    228                 break;
    229             case '\r':
    230                 m_discardTrailingNewline = true;
    231             case '\n':
    232                 lineLength = i - bufPos;
    233                 break;
    234             }
    235         }
    236 
    237         if (lineLength < 0)
    238             break;
    239 
    240         parseEventStreamLine(bufPos, fieldLength, lineLength);
    241         bufPos += lineLength + 1;
    242     }
    243 
    244     if (bufPos == bufSize)
    245         m_receiveBuf.clear();
    246     else if (bufPos)
    247         m_receiveBuf.remove(0, bufPos);
    248 }
    249 
    250 void EventSource::parseEventStreamLine(unsigned int bufPos, int fieldLength, int lineLength)
    251 {
    252     if (!lineLength) {
    253         if (!m_data.isEmpty())
    254             dispatchEvent(createMessageEvent());
    255         if (!m_eventName.isEmpty())
    256             m_eventName = "";
    257     } else if (fieldLength) {
    258         bool noValue = fieldLength < 0;
    259 
    260         String field(&m_receiveBuf[bufPos], noValue ? lineLength : fieldLength);
    261         int step;
    262         if (noValue)
    263             step = lineLength;
    264         else if (m_receiveBuf[bufPos + fieldLength + 1] != ' ')
    265             step = fieldLength + 1;
    266         else
    267             step = fieldLength + 2;
    268         bufPos += step;
    269         int valueLength = lineLength - step;
    270 
    271         if (field == "data") {
    272             if (m_data.size() > 0)
    273                 m_data.append('\n');
    274             if (valueLength)
    275                 m_data.append(&m_receiveBuf[bufPos], valueLength);
    276         } else if (field == "event")
    277             m_eventName = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
    278         else if (field == "id")
    279             m_lastEventId = valueLength ? String(&m_receiveBuf[bufPos], valueLength) : "";
    280         else if (field == "retry") {
    281             if (!valueLength)
    282                 m_reconnectDelay = defaultReconnectDelay;
    283             else {
    284                 String value(&m_receiveBuf[bufPos], valueLength);
    285                 bool ok;
    286                 unsigned long long retry = value.toUInt64(&ok);
    287                 if (ok)
    288                     m_reconnectDelay = retry;
    289             }
    290         }
    291     }
    292 }
    293 
    294 void EventSource::stop()
    295 {
    296     close();
    297 }
    298 
    299 PassRefPtr<MessageEvent> EventSource::createMessageEvent()
    300 {
    301     RefPtr<MessageEvent> event = MessageEvent::create();
    302     event->initMessageEvent(m_eventName.isEmpty() ? eventNames().messageEvent : AtomicString(m_eventName), false, false, SerializedScriptValue::create(String::adopt(m_data)), m_origin, m_lastEventId, 0, 0);
    303     return event.release();
    304 }
    305 
    306 EventTargetData* EventSource::eventTargetData()
    307 {
    308     return &m_eventTargetData;
    309 }
    310 
    311 EventTargetData* EventSource::ensureEventTargetData()
    312 {
    313     return &m_eventTargetData;
    314 }
    315 
    316 } // namespace WebCore
    317 
    318 #endif // ENABLE(EVENTSOURCE)
    319