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