public function call(\FutoIn\AsyncSteps $as, $name, $params, $upload_data = null, $download_stream = null, $timeout = null) { $ctx = new \StdClass(); $ctx->name = $name; $ctx->info = $this->raw_info; $ctx->upload_data = $upload_data; $ctx->download_stream = $download_stream; $params = (object) $params; // Create message //--- $as->add(function ($as) use($ctx, $params) { $this->ccmimpl->createMessage($as, $ctx, $params); }); // Perform request //--- $as->add(function ($as, $req) use($ctx, $upload_data, $download_stream, $timeout) { $curl_opts = array(CURLOPT_FORBID_REUSE => FALSE, CURLOPT_FRESH_CONNECT => FALSE, CURLOPT_NETRC => FALSE, CURLOPT_SSLVERSION => 3, CURLOPT_SSL_VERIFYPEER => TRUE, CURLOPT_FOLLOWLOCATION => FALSE, CURLOPT_CONNECTTIMEOUT_MS => 3000, CURLOPT_TIMEOUT_MS => 30000, CURLOPT_BINARYTRANSFER => true, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1) + $this->ccmimpl->curl_opts; $curl = curl_init(); $headers = array("Accept: application/futoin+json, */*"); $url = $this->raw_info->endpoint; if (substr($url, -1) !== '/') { $url .= '/'; } if ($upload_data) { // Encode according to FTN5: HTTP integration //--- // iface / ver / func $url .= str_replace(':', '/', $req->f); // /sec if (isset($req->sec)) { $url .= '/' . $req->sec; } // Params as query if (isset($req->p)) { $url .= '?' . http_build_query((array) $req->p); } //--- if (is_resource($upload_data)) { // Old C-style trick fseek($upload_data, -1, SEEK_END); $upload_size = ftell($upload_data) + 1; fseek($upload_data, 0, SEEK_SET); $curl_opts[CURLOPT_PUT] = true; $curl_opts[CURLOPT_CUSTOMREQUEST] = "POST"; $curl_opts[CURLOPT_INFILE] = $upload_data; $curl_opts[CURLOPT_INFILESIZE] = $upload_size; # Disable cURL-specific 1 second delay (empty 'Expect' does not work) $curl_opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_1_0; } else { $upload_size = strlen($upload_data); $curl_opts[CURLOPT_POST] = true; $curl_opts[CURLOPT_POSTFIELDS] = $upload_data; } $headers[] = "Content-Type: application/octet-stream"; $headers[] = 'Content-Length: ' . $upload_size; } else { $headers[] = "Content-Type: application/futoin+json"; $req = json_encode($req, JSON_FORCE_OBJECT | JSON_UNESCAPED_UNICODE); $curl_opts[CURLOPT_POST] = true; $curl_opts[CURLOPT_POSTFIELDS] = $req; } if ($download_stream) { $curl_opts[CURLOPT_FILE] = $download_stream; } else { $curl_opts[CURLOPT_RETURNTRANSFER] = true; // Limit size for security reasons $curl_opts[CURLOPT_PROGRESSFUNCTION] = function ($curl, $full_dlsize, $dlsize) { return $dlsize > self::MSG_MAXSIZE ? -1 : 0; }; } $curl_opts[CURLOPT_HTTPHEADER] = $headers; $curl_opts[CURLOPT_URL] = $url; curl_setopt_array($curl, $curl_opts); // cURL multi //--- if ($timeout === null) { $timeout = $curl_opts[CURLOPT_TIMEOUT_MS]; } if ((int) $timeout > 0) { $as->setTimeout($timeout); } $as->add(function ($as) use($curl) { $this->ccmimpl->multiCurlAdd($as, $curl); }); $as->add(function ($as, $curl, $info) use($ctx, $download_stream) { $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); $content_type = curl_getinfo($curl, CURLINFO_CONTENT_TYPE); $error = curl_error($curl); curl_close($curl); //--- if ($http_code !== 200) { $as->error(\FutoIn\Error::CommError, "HTTP:{$http_code} CURL:{$error}"); } elseif ($download_stream) { if ($info['result'] === CURLE_OK) { $as->success(true); } else { $as->error(\FutoIn\Error::CommError, "CURL:{$error}"); } } elseif ($content_type === 'application/futoin+json') { $rsp = json_decode($as->_futoin_response); if ($rsp) { $this->ccmimpl->onMessageResponse($as, $ctx, $rsp); } else { $as->error(\FutoIn\Error::CommError, "JSON:" . json_last_error_msg()); } } else { $this->ccmimpl->onDataResponse($as, $ctx); } }); }); }
/** * Process request, received for arbitrary channel, including unit-test generated * @param $as - AsyncSteps interface * @return void */ public function process(\FutoIn\AsyncSteps $as) { // Fundamental error if (!isset($as->reqinfo) || isset($as->_futoin_func_info)) { $as->error(\FutoIn\Error::InternalError, "Missing reqinfo"); } $as->add(function ($as) { $reqinfo = $as->reqinfo; // Step 1. Parsing interface and function info //--- $this->getInfo($as, $reqinfo); // Step 2. Security //--- $as->add(function ($as) use($reqinfo) { // TODO: check "sec" for HMAC -> MasterService // TODO: check for credentials auth -> $as->successStep(); }); // Step 3. Check constraints and function parameters //--- $as->add(function ($as) use($reqinfo) { $this->checkConstraints($as, $reqinfo); $this->checkParams($as, $reqinfo); $as->successStep(); }); // Step 4. Invoke implementation //--- $as->add(function ($as) use($reqinfo) { $func = $as->_futoin_func; $impl = $this->getImpl($as, $reqinfo); if (!method_exists($impl, $func)) { $as->error(\FutoIn\Error::InternalError, "Missing function implementation"); } if ($impl instanceof \FutoIn\Executor\AsyncImplementation) { $impl->{$func}($as, $reqinfo); } else { // TODO: run in separate task $result = $impl->{$func}($reqinfo); $as->success($result); } }); // Step 5. Gather result and sign succeeded response //--- $as->add(function ($as, $result = null) use($reqinfo) { if ($result !== null) { $r = $reqinfo->result(); foreach ($result as $k => $v) { $r->{$k} = $v; } } $this->checkResult($as, $reqinfo); $this->signResponse($as, $reqinfo); $this->packResponse($as, $reqinfo); $as->successStep(); }); }, function ($as, $err) { $reqinfo = $as->reqinfo; if (!isset(SpecTools::$standard_errors[$err]) && (!isset($as->_futoin_func_info) || !isset($as->_futoin_func_info->throws[$err]))) { $err = \FutoIn\Error::InternalError; } $rawrsp = $reqinfo->{RequestInfo::INFO_RAW_RESPONSE}; $rawrsp->e = $err; unset($rawrsp->r); // Even though request itself fails, send response $this->signResponse($as, $reqinfo); $this->packError($as, $reqinfo); $as->success(); }); }