# $SnapHashLicense:
#
# SnapLogic - Open source data services
#
# Copyright (C) 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: HttpPost.py 10330 2009-12-24 22:13:38Z grisha $
"""
HttpPost module POSTs or PUTs data from binary input view to a URL
and returns server response in output views
"""
import os
import snaplogic.components.FileUtils as FileUtils
from snaplogic.common import snap_log
from snaplogic.common.snap_exceptions import *
from snaplogic.common import version_info
from snaplogic.cc import component_api
from snaplogic.cc.component_api import ComponentAPI
import snaplogic.cc.prop as prop
from snaplogic.snapi_base import keys
from snaplogic.common.data_types import SnapString,SnapNumber
from snaplogic.common import snap_http_lib
from snaplogic import rp
from decimal import Decimal
ALLOWED_SCHEMES = ["http", "https"]
class HttpPost(ComponentAPI):
"""
HttpPost
"""
api_version = '1.0'
component_version = '1.1'
capabilities = {
ComponentAPI.CAPABILITY_INPUT_VIEW_LOWER_LIMIT : 1,
ComponentAPI.CAPABILITY_INPUT_VIEW_UPPER_LIMIT : 1,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_LOWER_LIMIT : 1,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_UPPER_LIMIT : 2,
ComponentAPI.CAPABILITY_ALLOW_PASS_THROUGH : False,
ComponentAPI.CAPABILITY_INPUT_VIEW_ALLOW_BINARY : True,
ComponentAPI.CAPABILITY_OUTPUT_VIEW_ALLOW_BINARY : True
}
component_description = "This component POSTs or PUTs data to a URL"
component_label = "HTTP Post/Put"
component_doc_uri = "https://www.snaplogic.org/trac/wiki/Documentation/%s/ComponentRef/HttpPost" % \
version_info.doc_uri_version
http_methods = ['POST', 'PUT']
def create_resource_template(self):
"""
Create HttpPost resource definition template. It consists of
outputURL: The URL to post or put data to
username: Credentials: username for that URL
password: Credentials: password for that URL
HttpMethod: One of [PUT, POST]
"""
self.set_property_def('outputURL',
prop.SimpleProp("Output URL", SnapString, "URL to post", None, True))
self.set_property_def("username",
prop.SimpleProp("Credentials: Username", SnapString,
"Username to use if credentials are needed for the accessing data"))
self.set_property_value("username", "")
self.set_property_def("password",
prop.SimpleProp("Credentials: Password", SnapString,
"Password to use if credentials are needed for the accessing data",
{'obfuscate' : 0}))
self.set_property_def('HttpMethod',
prop.SimpleProp("Http Method", SnapString, "Http Method",
{"lov": self.http_methods}, True))
# Set the default output views.
self.add_record_output_view_def("response_rec",
[
('response_code', SnapNumber, ""),
('response_reason', SnapString, ""),
('response_body', SnapString, ""),
('response_headers', SnapString, "")
], "Response", False)
# The *default* content type of binary output view is bogus SNAP_MAGIC_CONTENT_TYPE.
# This is to force whoever is reading from it to accept "*/*" because
# the actual content type is not known in advance; it should be whatever
# the URL this resource is *posting to* returns back, which may not be known
# in advance. If user knows the content type, they can override this by
# setting it for this view.
self.add_binary_output_view_def("response_bin", (rp.SNAP_MAGIC_CONTENT_TYPE,), 'Response', True)
self.add_binary_input_view_def('httppostin', ("*/*",), "Input", True)
def validate(self, err_obj):
"""
Validate HttpPost resource for correctness.
"""
# For now, this component only supports binary input view.
# It could potentially be expanded to do a form post from a record input view.
input_views = self.list_input_view_names()
input_view = self.get_input_view_def(input_views[keys.SINGLE_VIEW])
if input_view["is_record"] is not False:
err_obj.get_input_view_err()[input_views[keys.SINGLE_VIEW]].set_message(
"Input view has to be binary.")
# Make sure output views, if two are defined, are one of record, and the other -
# of binary kind.
output_views = self.list_output_view_names()
view1 = None
view2 = None
try:
view1 = self.get_output_view_def(output_views[0])
view2 = self.get_output_view_def(output_views[1])
if bool(int(view1["is_record"]) ^ int(view2["is_record"])) is False:
err_obj.get_output_view_err()[output_views[keys.SINGLE_VIEW]].set_message(
"If two output views are defined, one has to be a record and the other - a binary view.")
except IndexError:
# output views are optional
pass
def execute(self, input_views, output_views):
try:
input_view = input_views.values()[keys.SINGLE_VIEW]
except IndexError:
raise SnapComponentError("No output view connected.")
# We want to allow output view(s) to be defined but not connected.
view1 = None
view2 = None
output_view_bin = None
output_view_rec = None
try:
view1 = output_views.values()[0]
if view1.is_binary:
output_view_bin = view1
else:
output_view_rec = view1
view2 = output_views.values()[1]
if view2.is_binary:
output_view_bin = view2
else:
output_view_rec = view2
except IndexError:
pass
outputurl = self.get_property_value("outputURL")
username = self.get_property_value("username")
password = self.get_property_value("password")
outputurl = FileUtils.qualify_filename(outputurl)
method = self.get_property_value("HttpMethod")
# Validate filename URI scheme
error = FileUtils.validate_file_scheme(outputurl, ALLOWED_SCHEMES, ALLOWED_SCHEMES)
if error is not None:
raise SnapComponentError(error)
# "pass" input view's content type
ctype = input_view.get_binary_content_type()
hdr = {}
hdr["Content-Type"] = ctype
# If the user chooses to manually set the output view content type, then
# that should be specified in the Accept header of the POST request.
# Otherwise the default Accept is "*/*".
acceptStr = ""
first = True
if output_view_bin is not None:
for ctype in output_view_bin.list_binary_content_types():
if not first:
acceptStr += ','
if ctype == rp.SNAP_MAGIC_CONTENT_TYPE:
acceptStr += "*/*"
break
else:
acceptStr += ctype
first = False
hdr["Accept"] = acceptStr
hdr["Accept-Charset"] = "*"
# read all data from the input to post it in one chunk
res_line = input_view.read_binary(500)
result_data = ""
while res_line is not None:
result_data += res_line
res_line = input_view.read_binary(500)
if username != "":
_creds = (username, password)
else:
_creds = None
response = snap_http_lib.sendreq(method, outputurl, result_data, hdr, _creds)
response.getResponse()
code = response.getStatus()
reason = response.getReason()
headers_dic = response.getHeaders()
headers = ""
encoding = ""
respType = ""
for key in headers_dic.keys():
# Looking for charset in e.g.
# "Content-Type: text/html; charset=ISO-8859-4"
# of the first content type.
if key.lower() == "content-type" :
if respType == "":
respType = headers_dic[key]
if respType.find("text/") != -1 :
tpl = respType.partition('=')
encoding = tpl[2].strip()
# Lump all headers together, since we can't have dynamic number of fields in the output view
headers = headers + key + ":" + headers_dic[key] + '\n'
# If no charset found, default to utf-8
if encoding == "":
encoding = 'utf-8'
response_body = response.read()
if output_view_rec is not None:
out_rec = output_view_rec.create_record()
try:
out_rec['response_code'] = Decimal(code)
except:
out_rec['response_code'] = Decimal(500)
try:
out_rec['response_reason'] = unicode(reason, 'utf-8')
except:
out_rec['response_reason'] = None
try:
out_rec['response_body'] = unicode(response_body, encoding)
except:
out_rec['response_body'] = None
out_rec['response_headers'] = unicode(headers, 'utf-8')
output_view_rec.write_record(out_rec)
output_view_rec.completed()
# write to the binary output view
if output_view_bin is not None:
# The server response will have the content-type that the server
# has picked from the the choices in Accept header of the POST request.
# We use that type to write binary output (no need to sanity-check
# it because it will have to be one that we (user) supposedly accept.)
types = output_view_bin.list_binary_content_types()
output_view_bin.write_binary(response_body, types[0])
output_view_bin.completed()
response.close()
def upgrade_1_0_to_1_1(self):
"""
No-op upgrade only to change component doc URI during the upgrade
which will be by cc_info before calling this method.
"""
pass
|