mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-07 23:41:22 +08:00
create HttpSession as wrapper of requests.Session, in order to log more information of request and response
This commit is contained in:
149
ate/client.py
Normal file
149
ate/client.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from requests import Request, Response
|
||||||
|
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
|
||||||
|
RequestException)
|
||||||
|
|
||||||
|
from ate.exception import ParamsError
|
||||||
|
|
||||||
|
log_level = getattr(logging, "INFO")
|
||||||
|
logging.basicConfig(level=log_level)
|
||||||
|
absolute_http_url_regexp = re.compile(r"^https?://", re.I)
|
||||||
|
|
||||||
|
|
||||||
|
class ApiResponse(Response):
|
||||||
|
|
||||||
|
def raise_for_status(self):
|
||||||
|
if hasattr(self, 'error') and self.error:
|
||||||
|
raise self.error
|
||||||
|
Response.raise_for_status(self)
|
||||||
|
|
||||||
|
|
||||||
|
class HttpSession(requests.Session):
|
||||||
|
"""
|
||||||
|
Class for performing HTTP requests and holding (session-) cookies between requests (in order
|
||||||
|
to be able to log in and out of websites). Each request is logged so that ApiTestEngine can
|
||||||
|
display statistics.
|
||||||
|
|
||||||
|
This is a slightly extended version of `python-request <http://python-requests.org>`_'s
|
||||||
|
:py:class:`requests.Session` class and mostly this class works exactly the same. However
|
||||||
|
the methods for making requests (get, post, delete, put, head, options, patch, request)
|
||||||
|
can now take a *url* argument that's only the path part of the URL, in which case the host
|
||||||
|
part of the URL will be prepended with the HttpSession.base_url which is normally inherited
|
||||||
|
from a ApiTestEngine class' host property.
|
||||||
|
"""
|
||||||
|
def __init__(self, base_url=None, *args, **kwargs):
|
||||||
|
super(HttpSession, self).__init__(*args, **kwargs)
|
||||||
|
self.base_url = base_url if base_url else ""
|
||||||
|
|
||||||
|
def _build_url(self, path):
|
||||||
|
""" prepend url with hostname unless it's already an absolute URL """
|
||||||
|
if absolute_http_url_regexp.match(path):
|
||||||
|
return path
|
||||||
|
elif self.base_url:
|
||||||
|
return "%s%s" % (self.base_url, path)
|
||||||
|
else:
|
||||||
|
raise ParamsError("base url missed!")
|
||||||
|
|
||||||
|
def request(self, method, url, **kwargs):
|
||||||
|
"""
|
||||||
|
Constructs and sends a :py:class:`requests.Request`.
|
||||||
|
Returns :py:class:`requests.Response` object.
|
||||||
|
|
||||||
|
:param method:
|
||||||
|
method for the new :class:`Request` object.
|
||||||
|
:param url:
|
||||||
|
URL for the new :class:`Request` object.
|
||||||
|
:param params: (optional)
|
||||||
|
Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||||
|
:param data: (optional)
|
||||||
|
Dictionary or bytes to send in the body of the :class:`Request`.
|
||||||
|
:param headers: (optional)
|
||||||
|
Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||||
|
:param cookies: (optional)
|
||||||
|
Dict or CookieJar object to send with the :class:`Request`.
|
||||||
|
:param files: (optional)
|
||||||
|
Dictionary of ``'filename': file-like-objects`` for multipart encoding upload.
|
||||||
|
:param auth: (optional)
|
||||||
|
Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth.
|
||||||
|
:param timeout: (optional)
|
||||||
|
How long to wait for the server to send data before giving up, as a float, or \
|
||||||
|
a (`connect timeout, read timeout <user/advanced.html#timeouts>`_) tuple.
|
||||||
|
:type timeout: float or tuple
|
||||||
|
:param allow_redirects: (optional)
|
||||||
|
Set to True by default.
|
||||||
|
:type allow_redirects: bool
|
||||||
|
:param proxies: (optional)
|
||||||
|
Dictionary mapping protocol to the URL of the proxy.
|
||||||
|
:param stream: (optional)
|
||||||
|
whether to immediately download the response content. Defaults to ``False``.
|
||||||
|
:param verify: (optional)
|
||||||
|
if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
||||||
|
:param cert: (optional)
|
||||||
|
if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# prepend url with hostname unless it's already an absolute URL
|
||||||
|
url = self._build_url(url)
|
||||||
|
logging.info(" Start to {method} {url}".format(method=method, url=url))
|
||||||
|
logging.debug(" kwargs: {kwargs}".format(kwargs=kwargs))
|
||||||
|
# store meta data that is used when reporting the request to locust's statistics
|
||||||
|
request_meta = {}
|
||||||
|
|
||||||
|
# set up pre_request hook for attaching meta data to the request object
|
||||||
|
request_meta["method"] = method
|
||||||
|
request_meta["start_time"] = time.time()
|
||||||
|
|
||||||
|
response = self._send_request_safe_mode(method, url, **kwargs)
|
||||||
|
request_meta["url"] = (response.history and response.history[0] or response)\
|
||||||
|
.request.path_url
|
||||||
|
|
||||||
|
# record the consumed time
|
||||||
|
request_meta["response_time"] = int((time.time() - request_meta["start_time"]) * 1000)
|
||||||
|
|
||||||
|
# get the length of the content, but if the argument stream is set to True, we take
|
||||||
|
# the size from the content-length header, in order to not trigger fetching of the body
|
||||||
|
if kwargs.get("stream", False):
|
||||||
|
request_meta["content_size"] = int(response.headers.get("content-length") or 0)
|
||||||
|
else:
|
||||||
|
request_meta["content_size"] = len(response.content or "")
|
||||||
|
|
||||||
|
request_meta["request_headers"] = response.request.headers
|
||||||
|
request_meta["request_body"] = response.request.body
|
||||||
|
request_meta["status_code"] = response.status_code
|
||||||
|
request_meta["response_headers"] = response.headers
|
||||||
|
request_meta["response_content"] = response.content
|
||||||
|
|
||||||
|
logging.debug(" response: {response}".format(response=request_meta))
|
||||||
|
|
||||||
|
try:
|
||||||
|
response.raise_for_status()
|
||||||
|
except RequestException as e:
|
||||||
|
logging.error(" Failed to {method} {url}! exception msg: {exception}".format(
|
||||||
|
method=method, url=url, exception=str(e)))
|
||||||
|
else:
|
||||||
|
logging.info(
|
||||||
|
""" status_code: {}! response_time: {} ms, response_length: {} bytes"""\
|
||||||
|
.format(request_meta["status_code"], request_meta["response_time"], \
|
||||||
|
request_meta["content_size"]))
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def _send_request_safe_mode(self, method, url, **kwargs):
|
||||||
|
"""
|
||||||
|
Send a HTTP request, and catch any exception that might occur due to connection problems.
|
||||||
|
Safe mode has been removed from requests 1.x.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return requests.Session.request(self, method, url, **kwargs)
|
||||||
|
except (MissingSchema, InvalidSchema, InvalidURL):
|
||||||
|
raise
|
||||||
|
except RequestException as ex:
|
||||||
|
resp = ApiResponse()
|
||||||
|
resp.error = ex
|
||||||
|
resp.status_code = 0 # with this status_code, content returns None
|
||||||
|
resp.request = Request(method, url).prepare()
|
||||||
|
return resp
|
||||||
@@ -160,7 +160,8 @@ class Context(object):
|
|||||||
if "func" in data:
|
if "func" in data:
|
||||||
# this is a function, e.g. {"func": "gen_random_string", "args": [5]}
|
# this is a function, e.g. {"func": "gen_random_string", "args": [5]}
|
||||||
# function marker: "func" key in dict
|
# function marker: "func" key in dict
|
||||||
# the function will be called, and its return value will be binded to the variable.
|
# the function will be called, and its return value will be binded
|
||||||
|
# to the testcase context variable.
|
||||||
func_name = data['func']
|
func_name = data['func']
|
||||||
args = self.get_eval_value(data.get('args', []))
|
args = self.get_eval_value(data.get('args', []))
|
||||||
kargs = self.get_eval_value(data.get('kargs', {}))
|
kargs = self.get_eval_value(data.get('kargs', {}))
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import requests
|
|
||||||
|
|
||||||
from ate import exception, response
|
from ate import exception, response
|
||||||
|
from ate.client import HttpSession
|
||||||
from ate.context import Context
|
from ate.context import Context
|
||||||
|
|
||||||
|
|
||||||
class TestRunner(object):
|
class TestRunner(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.client = requests.Session()
|
self.client = HttpSession()
|
||||||
self.context = Context()
|
self.context = Context()
|
||||||
|
|
||||||
def init_context(self, config_dict, level):
|
def init_context(self, config_dict, level):
|
||||||
|
|||||||
34
test/test_client.py
Normal file
34
test/test_client.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from ate.client import HttpSession
|
||||||
|
from test.base import ApiServerUnittest
|
||||||
|
|
||||||
|
class TestHttpClient(ApiServerUnittest):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestHttpClient, self).setUp()
|
||||||
|
self.host = "http://127.0.0.1:5000"
|
||||||
|
self.api_client = HttpSession(self.host)
|
||||||
|
self.clear_users()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(TestHttpClient, self).tearDown()
|
||||||
|
|
||||||
|
def clear_users(self):
|
||||||
|
url = "%s/api/users" % self.host
|
||||||
|
return self.api_client.delete(url)
|
||||||
|
|
||||||
|
def create_user(self, uid, name, password):
|
||||||
|
url = "%s/api/users/%d" % (self.host, uid)
|
||||||
|
data = {
|
||||||
|
'name': name,
|
||||||
|
'password': password
|
||||||
|
}
|
||||||
|
return self.api_client.post(url, json=data)
|
||||||
|
|
||||||
|
def test_create_user_not_existed(self):
|
||||||
|
resp = self.create_user(1000, 'user1', '123456')
|
||||||
|
self.assertEqual(201, resp.status_code)
|
||||||
|
self.assertEqual(True, resp.json()['success'])
|
||||||
|
|
||||||
|
def test_create_user_existed(self):
|
||||||
|
resp = self.create_user(1000, 'user1', '123456')
|
||||||
|
resp = self.create_user(1000, 'user1', '123456')
|
||||||
|
self.assertEqual(500, resp.status_code)
|
||||||
Reference in New Issue
Block a user