blob: 3ee93663252b13e49f734ca5ff559d69713ea2b1 [file] [log] [blame]
#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Channel support classes.
Classes:
CreateChannelDispatcher:
Creates a dispatcher that is added to dispatcher chain. Handles polls from
the client to retrieve messages for a given channel.
"""
import cgi
import os
import urlparse
from google.appengine.api.channel.channel_service_stub import InvalidTokenError
from google.appengine.api.channel.channel_service_stub import TokenTimedOutError
CHANNEL_POLL_PATTERN = '/_ah/channel/dev(?:/.*)?'
CHANNEL_JSAPI_PATTERN = '/_ah/channel/jsapi'
def CreateChannelDispatcher(channel_service_stub):
"""Function to create channel dispatcher.
Args:
channel_service_stub: The service stub responsible for creating channels and
sending messages. This stub stores messages destined for channels, so we
query it when the client polls the _ah/channel/ dispatcher.
Returns:
New dispatcher capable of handling client polls for channel messages.
"""
from google.appengine.tools import old_dev_appserver
class ChannelDispatcher(old_dev_appserver.URLDispatcher):
"""Dispatcher that handles channel polls."""
def __init__(self, channel_service_stub):
"""Constructor.
Args:
channel_service_stub: The channel service that receives channel messages
from the application.
"""
self._channel_service_stub = channel_service_stub
def Dispatch(self,
request,
outfile,
base_env_dict=None):
"""Handle post dispatch.
This dispatcher handles requests under the /_ah/channel/ path. Currently
it handles 3 sub-paths:
connect: place-holder for parity with the java dev channel.
poll: return pending messages for the channel identified by the
channel parameter.
jsapi: return the javascript library to retrieve messages for the
channel.
Args:
request: The HTTP request.
outfile: The response file.
base_env_dict: Dictionary of CGI environment parameters if available.
Defaults to None.
"""
(unused_scheme, unused_netloc,
path, query,
unused_fragment) = urlparse.urlsplit(request.relative_url)
param_dict = cgi.parse_qs(query, True)
page = path.rsplit('/', 1)[-1]
if page == 'jsapi':
path = os.path.join(os.path.dirname(__file__), 'dev-channel-js.js')
outfile.write('Status: 200\r\n')
outfile.write('Content-type: text/javascript\r\n\r\n')
outfile.write(open(path).read())
elif page == 'dev':
token = param_dict['channel'][0]
token_error = None
try:
self._channel_service_stub.validate_token_and_extract_client_id(token)
except InvalidTokenError:
token_error = 'Invalid+token.'
except TokenTimedOutError:
token_error = 'Token+timed+out.'
if token_error is not None:
outfile.write('Status: 401 %s\r\n\r\n' % token_error)
return
outfile.write('Status: 200\r\n')
outfile.write('\r\n')
command = param_dict['command'][0]
if command == 'connect':
self._channel_service_stub.connect_channel(token)
outfile.write('1')
elif command == 'poll':
self._channel_service_stub.connect_channel(token)
if self._channel_service_stub.has_channel_messages(token):
outfile.write(self._channel_service_stub.pop_first_message(token))
return ChannelDispatcher(channel_service_stub)