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