xref: /OpenGrok/tools/src/main/python/opengrok_tools/utils/restful.py (revision 2d57dc692b4dd10e696ca6922510f6cecded1bfd)
1*2d57dc69SVladimir Kotal#
2*2d57dc69SVladimir Kotal# CDDL HEADER START
3*2d57dc69SVladimir Kotal#
4*2d57dc69SVladimir Kotal# The contents of this file are subject to the terms of the
5*2d57dc69SVladimir Kotal# Common Development and Distribution License (the "License").
6*2d57dc69SVladimir Kotal# You may not use this file except in compliance with the License.
7*2d57dc69SVladimir Kotal#
8*2d57dc69SVladimir Kotal# See LICENSE.txt included in this distribution for the specific
9*2d57dc69SVladimir Kotal# language governing permissions and limitations under the License.
10*2d57dc69SVladimir Kotal#
11*2d57dc69SVladimir Kotal# When distributing Covered Code, include this CDDL HEADER in each
12*2d57dc69SVladimir Kotal# file and include the License file at LICENSE.txt.
13*2d57dc69SVladimir Kotal# If applicable, add the following below this CDDL HEADER, with the
14*2d57dc69SVladimir Kotal# fields enclosed by brackets "[]" replaced with your own identifying
15*2d57dc69SVladimir Kotal# information: Portions Copyright [yyyy] [name of copyright owner]
16*2d57dc69SVladimir Kotal#
17*2d57dc69SVladimir Kotal# CDDL HEADER END
18*2d57dc69SVladimir Kotal#
19*2d57dc69SVladimir Kotal
20*2d57dc69SVladimir Kotal#
21*2d57dc69SVladimir Kotal# Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
22*2d57dc69SVladimir Kotal#
23*2d57dc69SVladimir Kotal
24*2d57dc69SVladimir Kotalimport json
25*2d57dc69SVladimir Kotalimport logging
26*2d57dc69SVladimir Kotal
27*2d57dc69SVladimir Kotalimport requests
28*2d57dc69SVladimir Kotal
29*2d57dc69SVladimir Kotalfrom .patterns import COMMAND_PROPERTY
30*2d57dc69SVladimir Kotalfrom .webutil import get_proxies
31*2d57dc69SVladimir Kotal
32*2d57dc69SVladimir KotalCONTENT_TYPE = 'Content-Type'
33*2d57dc69SVladimir KotalAPPLICATION_JSON = 'application/json'   # default
34*2d57dc69SVladimir Kotal
35*2d57dc69SVladimir Kotal
36*2d57dc69SVladimir Kotaldef do_api_call(verb, uri, params=None, headers=None, data=None):
37*2d57dc69SVladimir Kotal    """
38*2d57dc69SVladimir Kotal    Perform an API call. Will raise an exception if the request fails.
39*2d57dc69SVladimir Kotal    :param verb: string holding HTTP verb
40*2d57dc69SVladimir Kotal    :param uri: URI string
41*2d57dc69SVladimir Kotal    :param params: request parameters
42*2d57dc69SVladimir Kotal    :param headers: HTTP headers dictionary
43*2d57dc69SVladimir Kotal    :param data: data or None
44*2d57dc69SVladimir Kotal    :return: the result of the handler call, can be None
45*2d57dc69SVladimir Kotal    """
46*2d57dc69SVladimir Kotal    logger = logging.getLogger(__name__)
47*2d57dc69SVladimir Kotal
48*2d57dc69SVladimir Kotal    handler = getattr(requests, verb.lower())
49*2d57dc69SVladimir Kotal    if handler is None or not callable(handler):
50*2d57dc69SVladimir Kotal        raise Exception('Unknown HTTP verb: {}'.format(verb))
51*2d57dc69SVladimir Kotal
52*2d57dc69SVladimir Kotal    logger.debug("{} API call: {} with data '{}' and headers: {}".
53*2d57dc69SVladimir Kotal                 format(verb, uri, data, headers))
54*2d57dc69SVladimir Kotal    r = handler(
55*2d57dc69SVladimir Kotal        uri,
56*2d57dc69SVladimir Kotal        data=data,
57*2d57dc69SVladimir Kotal        params=params,
58*2d57dc69SVladimir Kotal        headers=headers,
59*2d57dc69SVladimir Kotal        proxies=get_proxies(uri)
60*2d57dc69SVladimir Kotal    )
61*2d57dc69SVladimir Kotal
62*2d57dc69SVladimir Kotal    if r is None:
63*2d57dc69SVladimir Kotal        raise Exception("API call failed")
64*2d57dc69SVladimir Kotal
65*2d57dc69SVladimir Kotal    r.raise_for_status()
66*2d57dc69SVladimir Kotal
67*2d57dc69SVladimir Kotal    return r
68*2d57dc69SVladimir Kotal
69*2d57dc69SVladimir Kotal
70*2d57dc69SVladimir Kotaldef call_rest_api(command, pattern, name):
71*2d57dc69SVladimir Kotal    """
72*2d57dc69SVladimir Kotal    Make RESTful API call. Occurrence of the pattern in the URI
73*2d57dc69SVladimir Kotal    (first part of the command) or data payload will be replaced by the name.
74*2d57dc69SVladimir Kotal
75*2d57dc69SVladimir Kotal    Default content type is application/json.
76*2d57dc69SVladimir Kotal
77*2d57dc69SVladimir Kotal    :param command: command (list of URI, HTTP verb, data payload)
78*2d57dc69SVladimir Kotal    :param pattern: pattern for command name and/or data substitution
79*2d57dc69SVladimir Kotal    :param name: command name
80*2d57dc69SVladimir Kotal    :return return value from given requests method
81*2d57dc69SVladimir Kotal    """
82*2d57dc69SVladimir Kotal
83*2d57dc69SVladimir Kotal    logger = logging.getLogger(__name__)
84*2d57dc69SVladimir Kotal
85*2d57dc69SVladimir Kotal    if not isinstance(command, dict) or command.get(COMMAND_PROPERTY) is None:
86*2d57dc69SVladimir Kotal        raise Exception("invalid command")
87*2d57dc69SVladimir Kotal
88*2d57dc69SVladimir Kotal    command = command[COMMAND_PROPERTY]
89*2d57dc69SVladimir Kotal
90*2d57dc69SVladimir Kotal    uri, verb, data, *_ = command
91*2d57dc69SVladimir Kotal    try:
92*2d57dc69SVladimir Kotal        headers = command[3]
93*2d57dc69SVladimir Kotal        if headers and not isinstance(headers, dict):
94*2d57dc69SVladimir Kotal            raise Exception("headers must be a dictionary")
95*2d57dc69SVladimir Kotal    except IndexError:
96*2d57dc69SVladimir Kotal        headers = {}
97*2d57dc69SVladimir Kotal
98*2d57dc69SVladimir Kotal    if headers is None:
99*2d57dc69SVladimir Kotal        headers = {}
100*2d57dc69SVladimir Kotal
101*2d57dc69SVladimir Kotal    if pattern and name:
102*2d57dc69SVladimir Kotal        uri = uri.replace(pattern, name)
103*2d57dc69SVladimir Kotal
104*2d57dc69SVladimir Kotal    header_names = [x.lower() for x in headers.keys()]
105*2d57dc69SVladimir Kotal
106*2d57dc69SVladimir Kotal    if data:
107*2d57dc69SVladimir Kotal        if CONTENT_TYPE.lower() not in header_names:
108*2d57dc69SVladimir Kotal            logger.debug("Adding header: {} = {}".
109*2d57dc69SVladimir Kotal                         format(CONTENT_TYPE, APPLICATION_JSON))
110*2d57dc69SVladimir Kotal            headers[CONTENT_TYPE] = APPLICATION_JSON
111*2d57dc69SVladimir Kotal
112*2d57dc69SVladimir Kotal        for (k, v) in headers.items():
113*2d57dc69SVladimir Kotal            if k.lower() == CONTENT_TYPE.lower():
114*2d57dc69SVladimir Kotal                if headers[k].lower() == APPLICATION_JSON.lower():
115*2d57dc69SVladimir Kotal                    logger.debug("Converting {} to JSON".format(data))
116*2d57dc69SVladimir Kotal                    data = json.dumps(data)
117*2d57dc69SVladimir Kotal                break
118*2d57dc69SVladimir Kotal
119*2d57dc69SVladimir Kotal        if pattern and name:
120*2d57dc69SVladimir Kotal            data = data.replace(pattern, name)
121*2d57dc69SVladimir Kotal        logger.debug("entity data: {}".format(data))
122*2d57dc69SVladimir Kotal
123*2d57dc69SVladimir Kotal    return do_api_call(verb, uri, headers=headers, data=data)
124