def _create_request_handle(request, cookies, timeout): """ Returns Curl object (easy handle) which is set up witc specified parameters. Request request -- request specification dict cookies -- cookies to add to request int timeot -- request timeout """ # it is not possible to take this callback out of this function, because of # curl API def __debug_callback(data_type, debug_data): prefixes = { pycurl.DEBUG_TEXT: b"* ", pycurl.DEBUG_HEADER_IN: b"< ", pycurl.DEBUG_HEADER_OUT: b"> ", pycurl.DEBUG_DATA_IN: b"<< ", pycurl.DEBUG_DATA_OUT: b">> ", } if data_type in prefixes: debug_output.write(prefixes[data_type]) debug_output.write(debug_data) if not debug_data.endswith(b"\n"): debug_output.write(b"\n") output = io.BytesIO() debug_output = io.BytesIO() cookies.update(request.cookies) handle = pycurl.Curl() handle.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTPS) handle.setopt(pycurl.TIMEOUT, timeout) handle.setopt(pycurl.URL, request.url.encode("utf-8")) handle.setopt(pycurl.WRITEFUNCTION, output.write) handle.setopt(pycurl.VERBOSE, 1) handle.setopt(pycurl.DEBUGFUNCTION, __debug_callback) handle.setopt(pycurl.SSL_VERIFYHOST, 0) handle.setopt(pycurl.SSL_VERIFYPEER, 0) handle.setopt(pycurl.NOSIGNAL, 1) # required for multi-threading handle.setopt(pycurl.HTTPHEADER, ["Expect: "]) if cookies: handle.setopt( pycurl.COOKIE, _dict_to_cookies(cookies).encode("utf-8") ) if request.data: handle.setopt( pycurl.COPYPOSTFIELDS, request.data.encode("utf-8") ) # add reference for request object and output bufers to handle, so later # we don't need to match these objects when they are returned from # pycurl after they've been processed # similar usage is in pycurl example: # https://github.com/pycurl/pycurl/blob/REL_7_19_0_3/examples/retriever-multi.py handle.request_obj = request handle.output_buffer = output handle.debug_buffer = debug_output return handle
def call_host(self, host, request, data, request_timeout=None): """ Send a request to a host host host address request command to be run on the host data command parameters, encoded by format_data_* method request timeout float timeout for request, if not set object property will be used """ def __debug_callback(data_type, debug_data): prefixes = { pycurl.DEBUG_TEXT: b"* ", pycurl.DEBUG_HEADER_IN: b"< ", pycurl.DEBUG_HEADER_OUT: b"> ", pycurl.DEBUG_DATA_IN: b"<< ", pycurl.DEBUG_DATA_OUT: b">> ", } if data_type in prefixes: debug_output.write(prefixes[data_type]) debug_output.write(debug_data) if not debug_data.endswith(b"\n"): debug_output.write(b"\n") output = io.BytesIO() debug_output = io.BytesIO() cookies = self.__prepare_cookies(host) timeout = (request_timeout if request_timeout is not None else self.request_timeout) url = "https://{host}:2224/{request}".format( host=("[{0}]".format(host) if ":" in host else host), request=request) handler = pycurl.Curl() handler.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTPS) handler.setopt(pycurl.TIMEOUT_MS, int(timeout * 1000)) handler.setopt(pycurl.URL, url.encode("utf-8")) handler.setopt(pycurl.WRITEFUNCTION, output.write) handler.setopt(pycurl.VERBOSE, 1) handler.setopt(pycurl.DEBUGFUNCTION, __debug_callback) handler.setopt(pycurl.SSL_VERIFYHOST, 0) handler.setopt(pycurl.SSL_VERIFYPEER, 0) handler.setopt(pycurl.NOSIGNAL, 1) # required for multi-threading handler.setopt(pycurl.HTTPHEADER, ["Expect: "]) if cookies: handler.setopt(pycurl.COOKIE, ";".join(cookies).encode("utf-8")) if data: handler.setopt(pycurl.COPYPOSTFIELDS, data.encode("utf-8")) msg = "Sending HTTP Request to: {url}" if data: msg += "\n--Debug Input Start--\n{data}\n--Debug Input End--" self._logger.debug(msg.format(url=url, data=data)) self._reporter.process(reports.node_communication_started(url, data)) result_msg = ( "Finished calling: {url}\nResponse Code: {code}" + "\n--Debug Response Start--\n{response}\n--Debug Response End--") try: handler.perform() response_data = output.getvalue().decode("utf-8") response_code = handler.getinfo(pycurl.RESPONSE_CODE) self._logger.debug( result_msg.format(url=url, code=response_code, response=response_data)) self._reporter.process( reports.node_communication_finished(url, response_code, response_data)) if response_code == 400: # old pcsd protocol: error messages are commonly passed in plain # text in response body with HTTP code 400 # we need to be backward compatible with that raise NodeCommandUnsuccessfulException(host, request, response_data.rstrip()) elif response_code == 401: raise NodeAuthenticationException( host, request, "HTTP error: {0}".format(response_code)) elif response_code == 403: raise NodePermissionDeniedException( host, request, "HTTP error: {0}".format(response_code)) elif response_code == 404: raise NodeUnsupportedCommandException( host, request, "HTTP error: {0}".format(response_code)) elif response_code >= 400: raise NodeCommunicationException( host, request, "HTTP error: {0}".format(response_code)) return response_data except pycurl.error as e: # In pycurl versions lower then 7.19.3 it is not possible to set # NOPROXY option. Therefore for the proper support of proxy settings # we have to use environment variables. if is_proxy_set(os.environ): self._logger.warning("Proxy is set") self._reporter.process( reports.node_communication_proxy_is_set()) errno, reason = e.args msg = "Unable to connect to {node} ({reason})" self._logger.debug(msg.format(node=host, reason=reason)) self._reporter.process( reports.node_communication_not_connected(host, reason)) if errno == pycurl.E_OPERATION_TIMEDOUT: raise NodeConnectionTimedOutException(host, request, reason) else: raise NodeConnectionException(host, request, reason) finally: debug_data = debug_output.getvalue().decode("utf-8", "ignore") self._logger.debug( ("Communication debug info for calling: {url}\n" "--Debug Communication Info Start--\n" "{data}\n" "--Debug Communication Info End--").format(url=url, data=debug_data)) self._reporter.process( reports.node_communication_debug_info(url, debug_data))