0001
0002
0003import os
0004import re
0005import sys
0006import urlparse
0007import urllib
0008from command import Command, BadCommand
0009from paste.deploy import loadapp, loadserver
0010from paste.wsgilib import raw_interactive
0011
0012class RequestCommand(Command):
0013
0014 min_args = 2
0015 usage = 'CONFIG_FILE URL [OPTIONS/ARGUMENTS]'
0016 takes_config_file = 1
0017 summary = "Run a request for the described application"
0018 description = """\
0019 This command makes an artifical request to a web application that
0020 uses a paste.deploy configuration file for the server and
0021 application.
0022
0023 Use 'paster request config.ini /url' to request /url. Use
0024 'paster post config.ini /url < data' to do a POST with the given
0025 request body.
0026
0027 If the URL is relative (doesn't begin with /) it is interpreted as
0028 relative to /.command/. The variable environ['paste.command_request']
0029 will be set to True in the request, so your application can distinguish
0030 these calls from normal requests.
0031
0032 Note that you can pass options besides the options listed here; any unknown
0033 options will be passed to the application in environ['QUERY_STRING'].
0034 """
0035
0036 parser = Command.standard_parser(quiet=True)
0037 parser.add_option('-n', '--app-name',
0038 dest='app_name',
0039 metavar='NAME',
0040 help="Load the named application (default main)")
0041 parser.add_option('--config-var',
0042 dest='config_vars',
0043 metavar='NAME:VALUE',
0044 action='append',
0045 help="Variable to make available in the config for %()s substitution "
0046 "(you can use this option multiple times)")
0047 parser.add_option('--header',
0048 dest='headers',
0049 metavar='NAME:VALUE',
0050 action='append',
0051 help="Header to add to request (you can use this option multiple times)")
0052 parser.add_option('--display-headers',
0053 dest='display_headers',
0054 action='store_true',
0055 help='Display headers before the response body')
0056
0057 ARG_OPTIONS = ['-n', '--app-name', '--config-var', '--header']
0058 OTHER_OPTIONS = ['--display-headers']
0059
0060
0061
0062
0063 _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I)
0064
0065 def command(self):
0066 vars = {}
0067 app_spec = self.args[0]
0068 url = self.args[1]
0069 url = urlparse.urljoin('/.command/', url)
0070 if self.options.config_vars:
0071 for item in self.option.config_vars:
0072 if ':' not in item:
0073 raise BadCommand(
0074 "Bad option, should be name:value : --config-var=%s" % item)
0075 name, value = item.split(':', 1)
0076 vars[name] = value
0077 headers = {}
0078 if self.options.headers:
0079 for item in self.options.headers:
0080 if ':' not in item:
0081 raise BadCommand(
0082 "Bad option, should be name:value : --header=%s" % item)
0083 name, value = item.split(':', 1)
0084 headers[name] = value.strip()
0085 if not self._scheme_re.search(app_spec):
0086 app_spec = 'config:'+app_spec
0087 if self.options.app_name:
0088 if '#' in app_spec:
0089 app_spec = app_spec.split('#', 1)[0]
0090 app_spec = app_spec + '#' + options.app_name
0091 app = loadapp(app_spec, relative_to=os.getcwd(), global_conf=vars)
0092 if self.command_name.lower() == 'post':
0093 request_method = 'POST'
0094 else:
0095 request_method = 'GET'
0096 qs = []
0097 for item in self.args[2:]:
0098 if '=' in item:
0099 item = urllib.quote(item.split('=', 1)[0]) + '=' + urllib.quote(item.split('=', 1)[1])
0100 else:
0101 item = urllib.quote(item)
0102 qs.append(item)
0103 qs = '&'.join(qs)
0104
0105 environ = {
0106 'REQUEST_METHOD': request_method,
0107
0108 'CONTENT_TYPE': 'text/plain',
0109 'wsgi.run_once': True,
0110 'wsgi.multithread': False,
0111 'wsgi.multiprocess': False,
0112 'wsgi.errors': sys.stderr,
0113 'QUERY_STRING': qs,
0114 'HTTP_ACCEPT': 'text/plain;q=1.0, */*;q=0.1',
0115 'paste.command_request': True,
0116 }
0117 if request_method == 'POST':
0118 environ['wsgi.input'] = sys.stdin
0119 environ['CONTENT_LENGTH'] = '-1'
0120 for name, value in headers.items():
0121 if name.lower() == 'content-type':
0122 name = 'CONTENT_TYPE'
0123 else:
0124 name = 'HTTP_'+name.upper().replace('-', '_')
0125 environ[name] = value
0126
0127 status, headers, output, errors = raw_interactive(app, url, **environ)
0128 assert not errors, "errors should be printed directly to sys.stderr"
0129 if self.options.display_headers:
0130 for name, value in headers:
0131 sys.stdout.write('%s: %s\n' % (name, value))
0132 sys.stdout.write('\n')
0133 sys.stdout.write(output)
0134 sys.stdout.flush()
0135 status_int = int(status.split()[0])
0136 if status_int != 200:
0137 return status_int
0138
0139 def parse_args(self, args):
0140 if args == ['-h']:
0141 Command.parse_args(self, args)
0142 return
0143
0144 normal_args = []
0145
0146 extra_args = []
0147
0148 pos_args = 0
0149 while args:
0150 start = args[0]
0151 if not start.startswith('-'):
0152 if pos_args < 2:
0153 pos_args += 1
0154 normal_args.append(start)
0155 args.pop(0)
0156 continue
0157 else:
0158 normal_args.append(start)
0159 args.pop(0)
0160 continue
0161 else:
0162 found = False
0163 for option in self.ARG_OPTIONS:
0164 if start == option:
0165 normal_args.append(start)
0166 args.pop(0)
0167 if not args:
0168 raise BadCommand(
0169 "Option %s takes an argument" % option)
0170 normal_args.append(args.pop(0))
0171 found = True
0172 break
0173 elif start.startswith(option+'='):
0174 normal_args.append(start)
0175 args.pop(0)
0176 found = True
0177 break
0178 if found:
0179 continue
0180 if start in self.OTHER_OPTIONS:
0181 normal_args.append(start)
0182 args.pop(0)
0183 continue
0184 extra_args.append(start)
0185 args.pop(0)
0186 Command.parse_args(self, normal_args)
0187
0188 self.args = self.args + extra_args