# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 2008-2009, SnapLogic, Inc. All rights reserved.
#
# See http://www.snaplogic.org for more information about
# the SnapLogic project.
#
# This program is free software, distributed under the terms of
# the GNU General Public License Version 2. See the LEGAL file
# at the top of the source tree.
#
# "SnapLogic" is a trademark of SnapLogic, Inc.
#
#
# $
# $Id: http_pipe_common.py 7878 2009-06-12 20:30:20Z dhiraj $
__docformat__ = "epytext en"
import os.path
import urlparse
import urllib
from snaplogic.server import RhResponse
from snaplogic.server import repository
from snaplogic.server import auth
from snaplogic.snapi_base import keys
from snaplogic.snapi_base.resdef import ResDef
from snaplogic import rp
class PipeSessionInfo(object):
"""
Objects of this class type contain information needed to process client GET/POST requests.
The HTTP session from client to SnapLogic data service can involve a lot of
modifying factors like content-type, accept headers, sn.* params etc. Objects of this
class are meant to be instantiated for each such HTTP session and are used to keep track
of all these modifying factors, and are passed around various methods of the data service
which need to be aware of these modifying factors while processing the HTTP request.
"""
def __init__(self, http_req, pipe_to_http_uri_prefix):
self.remaining_path = http_req.path[len(pipe_to_http_uri_prefix):]
self.http_req = http_req
self.rp_writer = None
self.starting_rec = None
"""
The number of the starting record. If it is None, then the first record
of the stream will be the starting record.
"""
self.rec_limit = None
"""
The maximum number of records to return. If it is None, then no upper limit
is placed on the number of records being returned.
"""
self.count_req = None
"""
If count_req is not set to None, then it specifies the type of objects to count.
Currently, the only type of object that is counted is "records".
"""
self.content_type = None
"""The content type being requested."""
self.rp_header = None
"""
Some RPs (like JSONP) allow a header to be specified at initialization time. This header
is returned by the RP at the beginning of the output stream.
"""
self.stream_footer = ""
"""
Some RPs (like JSONP) allow a footer to be specified at initialization time. This footer
is returned by the RP at the end output stream, to mark the end of output.
"""
self.num_written_out = 0
"""Number of records or bytes written out so far."""
self.current_record_num = 0
"""The current record being processed (1 to n)."""
self.sent_headers = False
"""Flag that indicates if response header has been sent or not."""
self.output_is_record = None
"""Flag if True, means that the output view is record mode view (as opposed to binary mode)."""
self.input_is_record = None
"""Flag if True, means that the input view is record mode view (as opposed to binary mode)."""
self.output_view_name = None
"""Name specified by the request for the resource output view (if any)."""
self.input_view_name = None
"""Name specified by the request for the resource input view (if any)."""
self.resdef = None
"""ResDef being executed."""
# Params that can only be specified by the client when an output stream is being requested.
snap_output_stream_params = ["sn.start", "sn.limit", "sn.count", "sn.stream_header",
"sn.stream_footer", "sn.content_type"]
# Other SnapLogic specific params.
snap_other_params = ["sn.output_view"]
# All SnapLogic params.
snap_all_params = snap_output_stream_params + snap_other_params
def process_http_params(http_req, pinfo):
"""
Process the params provided with HTTP request.
@param http_req: HTTP request object.
@type http_req: L{HttpRequest}
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
@return: Return None, if the processing went fine, else, return RhResponse
object with data and code to be written to client.
@rtype: L{RhResponse}
"""
for a in http_req.params:
if a.startswith("sn.") and a not in snap_all_params:
return RhResponse(http_req.BAD_REQUEST,
"Parameter '%s' is not a valid SnapLogic parameter" % a)
view_name = http_req.params.get("sn.output_view")
if view_name is not None:
if http_req.method != 'POST':
return RhResponse(http_req.BAD_REQUEST,
"Parameter 'sn.output_view' can only be specified for POST requests")
else:
pinfo.output_view_name = view_name
if pinfo.output_view_name is None:
# Some params can only be specified when an output stream is being returned.
for param_name in snap_output_stream_params:
if param_name in http_req.params:
return RhResponse(http_req.BAD_REQUEST,
"Parameter '%s' can only be specified if an output stream is being requested"
% param_name)
if "sn.start" in http_req.params:
if not http_req.params["sn.start"].isdigit():
return RhResponse(http_req.BAD_REQUEST, "sn.start should be a positive, non zero numeric value <%s>"
% http_req.params["sn.start"])
pinfo.starting_rec = long(http_req.params["sn.start"])
if pinfo.starting_rec < 1:
return RhResponse(http_req.BAD_REQUEST, "sn.start should be a positive, non zero numeric value <%s>"
% http_req.params["sn.start"])
if "sn.limit" in http_req.params:
if not http_req.params["sn.limit"].isdigit():
return RhResponse(http_req.BAD_REQUEST, "sn.limit should be a positive numeric value <%s>"
% http_req.params["sn.limit"])
pinfo.rec_limit = long(http_req.params["sn.limit"])
if pinfo.rec_limit < 0:
return RhResponse(http_req.BAD_REQUEST, "sn.limit should be a positive numeric value <%s>"
% http_req.params["sn.limit"])
if "sn.count" in http_req.params:
if pinfo.starting_rec is not None:
return RhResponse(http_req.BAD_REQUEST,
"Request cannot have both sn.start and sn.count parameters.")
if pinfo.rec_limit is not None:
return RhResponse(http_req.BAD_REQUEST,
"Request cannot have both sn.limit and sn.count parameters.")
if http_req.params["sn.count"].lower() == "records":
pinfo.count_req = "records"
else:
return RhResponse(http_req.BAD_REQUEST, "sn.count takes the argument \"records\" and not \"%s\""
% http_req.params["sn.count"])
if "sn.stream_header" in http_req.params:
pinfo.rp_header = http_req.params["sn.stream_header"]
if "sn.stream_footer" in http_req.params:
pinfo.stream_footer = http_req.params["sn.stream_footer"]
if "sn.content_type" in http_req.params:
pinfo.content_type = http_req.params["sn.content_type"].lower()
else:
# TODO: Code copied from http_request.py. would be nice to have this as a utility method in http_req.
if http_req.human_request:
# IE for some reason doesn't indicate text/html as an acceptable
# content type, only '*/*' and other junk. So, if the human flag
# is set, we just have to manually choose the appropriate content
# type. Otherwise, we would default to 'application/x-snap-asn1' and IE
# wouldn't like that.
pinfo.content_type = "text/html"
else:
pinfo.content_type = http_req.http_accept
return None
def process_http_uri(http_req, pinfo):
"""
Process the URI provided with the HTTP request to feed URI.
@param http_req: HTTP request object.
@type http_req: L{HttpRequest}
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
@return: Return None, if the processing went fine, else, return RhResponse
object with data and code to be written to client.
@rtype: L{RhResponse}
"""
# Get resource URI and view name.
(pinfo.resource_uri, view_name) = os.path.split(pinfo.remaining_path)
if not pinfo.resource_uri:
return RhResponse(http_req.NOT_FOUND, "Please specify a resource in the URI")
if not view_name:
return RhResponse(http_req.NOT_FOUND, "URI '%s' does not specify a view name" % http_req.path)
# Before we waste any more cycles on this request, check the authorization for this request.
perms = auth.check_uri(pinfo.resource_uri, http_req.username, http_req.groups)
if not auth.can_exec(perms):
return RhResponse(http_req.UNAUTHORIZED, "Not authorized to access resource '%s' in URI '%s'" %
(pinfo.resource_uri, http_req.path))
# Next, lets make sure that the resource still exists in the repository.
rep = repository.get_instance()
ret = rep.read_resources([pinfo.resource_uri])[keys.SUCCESS]
if pinfo.resource_uri not in ret:
return RhResponse(http_req.NOT_FOUND, "URI '%s' specifies unknown resource '%s'" %
(http_req.path, pinfo.resource_uri))
pinfo.guid = ret[pinfo.resource_uri][keys.GUID]
pinfo.gen_id = str(ret[pinfo.resource_uri][keys.GEN_ID])
resdef_dict = ret[pinfo.resource_uri][keys.RESDEF]
resdef = ResDef(resdef_dict)
pinfo.resdef = resdef
if http_req.method == 'GET':
# This is a request to the output view of a resource.
if view_name not in resdef.list_output_view_names():
return RhResponse(http_req.NOT_FOUND, "Output view '%s' not found in resource '%s'. Available views : %s."
% (view_name, pinfo.resource_uri, resdef.list_output_view_names()))
pinfo.output_is_record = resdef.output_is_record_mode(view_name)
pinfo.output_view_name = view_name
elif http_req.method == 'POST':
# This is a request to the input view of a resource.
if view_name not in resdef.list_input_view_names():
return RhResponse(http_req.NOT_FOUND, "Input view '%s' not found in resource '%s'. Available views : %s."
% (view_name, pinfo.resource_uri, resdef.list_input_view_names()))
pinfo.input_is_record = resdef.input_is_record_mode(view_name)
pinfo.input_view_name = view_name
ret = process_http_params(http_req, pinfo)
if ret is not None:
return ret
if http_req.method == 'POST' and pinfo.output_view_name is not None:
if pinfo.output_view_name not in resdef.list_output_view_names():
return RhResponse(http_req.BAD_REQUEST,
"Output view '%s' not found in resource '%s'. Available views : %s."
% (pinfo.output_view_name, pinfo.resource_uri, resdef.list_output_view_names()))
pinfo.output_is_record = resdef.output_is_record_mode(pinfo.output_view_name)
#
# First, lets negotiate content for output stream. Irrespective of whether we are handling a POST or
# a GET, we need to have an output stream, as even a POST needs to send data back, like some kind of
# error message.
#
if pinfo.output_is_record == True:
if pinfo.content_type == rp.SNAP_ASN1_CONTENT_TYPE:
return RhResponse(http_req.UNSUPPORTED_MEDIA_TYPE, "URI %s does not support content type '%s'."
% (http_req.path, pinfo.content_type))
out_rp = rp.get_rp(pinfo.content_type)
if out_rp is None:
return RhResponse(http_req.UNSUPPORTED_MEDIA_TYPE, "URI %s does not support content type '%s'."
% (http_req.path, pinfo.content_type))
pinfo.content_type = out_rp.CONTENT_TYPE
elif pinfo.output_is_record == False:
# This is either binary output view or a POST that is not reading any output view. For
# this scenario the params sn.count, sn.start and sn.limits are not supported.
if pinfo.rec_limit is not None:
return RhResponse(http_req.BAD_REQUEST, "Request to binary view cannot have sn.limit parameter.")
if pinfo.starting_rec is not None:
return RhResponse(http_req.BAD_REQUEST, "Request to binary view cannot have sn.start parameter.")
if pinfo.count_req is not None:
return RhResponse(http_req.BAD_REQUEST, "Request to binary view cannot have sn.count parameter.")
ctypes = resdef.get_output_view_content_types(view_name)
matched_ctx = rp.negotiate_content_type_str(pinfo.content_type, ctypes)
if matched_ctx is None:
return RhResponse(http_req.UNSUPPORTED_MEDIA_TYPE, "URI %s does not support content type '%s'."
% (http_req.path, pinfo.content_type))
pinfo.content_type = matched_ctx
else:
# No output view is going to be redirected here. We can respond with one of our RP supported
# content types.
out_rp = rp.get_rp(pinfo.content_type)
if out_rp is None:
return RhResponse(http_req.UNSUPPORTED_MEDIA_TYPE, "URI %s does not support content type '%s'."
% (http_req.path, pinfo.content_type))
def write_http_output(pinfo, data):
"""
This method writes output to the client that made the request to this component.
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
@param data: The data that needs to be written out.
@type data: list or str
"""
if not pinfo.sent_headers:
send_response_headers(pinfo, 200)
if pinfo.rp_writer is not None:
pinfo.rp_writer.write(data)
else:
pinfo.http_req._output.write(data)
def end_output(pinfo):
"""
Puts an end to the data being returned to client.
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
"""
if not pinfo.sent_headers:
send_response_headers(pinfo, 200)
if pinfo.rp_writer is not None:
pinfo.rp_writer.end(pinfo.stream_footer)
pinfo.rp_writer = None
def send_response_headers(pinfo, http_code):
"""
Sends response headers for the HTTP request from the client.
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
@param http_code: The HTTP code to be returned in the response HTTP header.
@type http_code: int
"""
pinfo.http_req.send_response_headers(http_code, (('content-type', pinfo.content_type),), False)
pinfo.sent_headers = True
if pinfo.output_is_record:
r = rp.get_rp(pinfo.content_type)
pinfo.rp_writer = r.Writer(pinfo.http_req._output, None, False, { 'record_stream' : 'yes' })
if pinfo.rp_header:
pinfo.rp_writer.initialize(pinfo.rp_header)
else:
pinfo.rp_writer.initialize()
def write_rec_to_http(pinfo, rec):
"""
Writes a record to the client that made the request.
@param pinfo: The current client session information.
@type pinfo: L{PipeSessionInfo}
@param rec: The raw record (tuple) to be written out
@type rec: tuple.
@return: True, if the number of records written has hit the limit specified by the client, else, False.
@rtype: bool
"""
if pinfo.count_req is not None:
# This is only a request to count records
return False
if pinfo.starting_rec is not None and pinfo.current_record_num < pinfo.starting_rec:
return False
if pinfo.rec_limit is not None and (pinfo.num_written_out >= pinfo.rec_limit):
return True
write_http_output(pinfo, rec)
pinfo.num_written_out += 1
return False
|