diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a96666de6283955363fa85d13b2e58d46a61011..8dab01f14a7a82213ae92d5fbcfce619e9939a96 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,7 +75,6 @@ include(generic) include(flags) if (NOT CLIENT_ONLY) -include(external/mklml) include(paddlepaddle) include(external/opencv) endif() diff --git a/cmake/external/mklml.cmake b/cmake/external/mklml.cmake deleted file mode 100644 index 2caff27357687018f29c1efc55b7b82c9dc3ccf6..0000000000000000000000000000000000000000 --- a/cmake/external/mklml.cmake +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright (c) 2017 PaddlePaddle Authors. All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -IF(NOT ${WITH_MKLML}) - return() -ENDIF(NOT ${WITH_MKLML}) - -IF(APPLE) - MESSAGE(WARNING "Mac is not supported with MKLML in Paddle yet. Force WITH_MKLML=OFF.") - SET(WITH_MKLML OFF CACHE STRING "Disable MKLML package in MacOS" FORCE) - return() -ENDIF() - -INCLUDE(ExternalProject) -SET(MKLML_DST_DIR "mklml") -SET(MKLML_INSTALL_ROOT "${THIRD_PARTY_PATH}/install") -SET(MKLML_INSTALL_DIR ${MKLML_INSTALL_ROOT}/${MKLML_DST_DIR}) -SET(MKLML_ROOT ${MKLML_INSTALL_DIR}) -SET(MKLML_INC_DIR ${MKLML_ROOT}/include) -SET(MKLML_LIB_DIR ${MKLML_ROOT}/lib) -SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH}" "${MKLML_ROOT}/lib") - -SET(TIME_VERSION "2019.0.1.20181227") -IF(WIN32) - SET(MKLML_VER "mklml_win_${TIME_VERSION}" CACHE STRING "" FORCE) - SET(MKLML_URL "https://paddlepaddledeps.cdn.bcebos.com/${MKLML_VER}.zip" CACHE STRING "" FORCE) - SET(MKLML_LIB ${MKLML_LIB_DIR}/mklml.lib) - SET(MKLML_IOMP_LIB ${MKLML_LIB_DIR}/libiomp5md.lib) - SET(MKLML_SHARED_LIB ${MKLML_LIB_DIR}/mklml.dll) - SET(MKLML_SHARED_IOMP_LIB ${MKLML_LIB_DIR}/libiomp5md.dll) -ELSE() - SET(MKLML_VER "mklml_lnx_${TIME_VERSION}" CACHE STRING "" FORCE) - SET(MKLML_URL "http://paddlepaddledeps.cdn.bcebos.com/${MKLML_VER}.tgz" CACHE STRING "" FORCE) - SET(MKLML_LIB ${MKLML_LIB_DIR}/libmklml_intel.so) - SET(MKLML_IOMP_LIB ${MKLML_LIB_DIR}/libiomp5.so) - SET(MKLML_SHARED_LIB ${MKLML_LIB_DIR}/libmklml_intel.so) - SET(MKLML_SHARED_IOMP_LIB ${MKLML_LIB_DIR}/libiomp5.so) -ENDIF() - -SET(MKLML_PROJECT "extern_mklml") -MESSAGE(STATUS "MKLML_VER: ${MKLML_VER}, MKLML_URL: ${MKLML_URL}") -SET(MKLML_SOURCE_DIR "${THIRD_PARTY_PATH}/mklml") -SET(MKLML_DOWNLOAD_DIR "${MKLML_SOURCE_DIR}/src/${MKLML_PROJECT}") - -ExternalProject_Add( - ${MKLML_PROJECT} - ${EXTERNAL_PROJECT_LOG_ARGS} - PREFIX ${MKLML_SOURCE_DIR} - URL ${MKLML_URL} - DOWNLOAD_DIR ${MKLML_DOWNLOAD_DIR} - DOWNLOAD_NO_PROGRESS 1 - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - UPDATE_COMMAND "" - INSTALL_COMMAND - ${CMAKE_COMMAND} -E copy_directory ${MKLML_DOWNLOAD_DIR}/include ${MKLML_INC_DIR} && - ${CMAKE_COMMAND} -E copy_directory ${MKLML_DOWNLOAD_DIR}/lib ${MKLML_LIB_DIR} -) - -INCLUDE_DIRECTORIES(${MKLML_INC_DIR}) - -ADD_LIBRARY(mklml SHARED IMPORTED GLOBAL) -SET_PROPERTY(TARGET mklml PROPERTY IMPORTED_LOCATION ${MKLML_LIB}) -ADD_DEPENDENCIES(mklml ${MKLML_PROJECT}) -LIST(APPEND external_project_dependencies mklml) diff --git a/cube/cube-transfer/src/cube-transfer.go b/cube/cube-transfer/src/cube-transfer.go index ddc17c90740acb1f0d22ba4776a101e0b38624df..e199a9ce1fefa566ab5c3b8b168368b230173f20 100755 --- a/cube/cube-transfer/src/cube-transfer.go +++ b/cube/cube-transfer/src/cube-transfer.go @@ -16,8 +16,8 @@ package main import ( "fmt" - "github.com/docopt/docopt-go" "github.com/Badangel/logex" + "github.com/docopt/docopt-go" "os" "path/filepath" "runtime" @@ -64,6 +64,7 @@ Log options: // settings: if opts["-p"] == nil { + logex.Fatal("ERROR: -p PORT must be set!") fmt.Fprintln(os.Stderr, "ERROR: -p PORT must be set!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -72,6 +73,7 @@ Log options: logex.Notice(">>> port:", transfer.Port) if opts["--config"] == nil { + logex.Fatal("ERROR: --config config_file must be set!") fmt.Fprintln(os.Stderr, "ERROR: --config config_file must be set!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -81,6 +83,7 @@ Log options: configMgr.Init(opts["--config"].(string)) transfer.Dict.DictName = configMgr.Read("default", "dict_name") if transfer.Dict.DictName == "" { + logex.Fatal("ERROR: nead [default] DictName in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] DictName in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -89,6 +92,7 @@ Log options: transfer.Dict.DictMode = configMgr.Read("default", "mode") if transfer.Dict.DictMode == "" { + logex.Fatal("ERROR: nead [default] DictMode in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] DictMode in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -97,28 +101,31 @@ Log options: transfer.Dict.DownloadMode = configMgr.Read("default", "download_mode") if transfer.Dict.DownloadMode != "http" && transfer.Dict.DownloadMode != "ftp" { + logex.Fatal("ERROR: nead [default] download_mode in config_file! only support ftp or http") fmt.Fprintln(os.Stderr, "ERROR: nead [default] download_mode in config_file! only support ftp or http") fmt.Fprintln(os.Stderr, usage) os.Exit(1) } logex.Notice(">>> DownloadMode:", transfer.Dict.DownloadMode) - transfer.Dict.WgetPort = configMgr.Read("default", "wget_port") - if transfer.Dict.WgetPort == "" { - fmt.Fprintln(os.Stderr, "ERROR: nead [default] wget_port in config_file!") - fmt.Fprintln(os.Stderr, usage) - os.Exit(1) - } - var wget_port int - wget_port, err = strconv.Atoi(transfer.Dict.WgetPort) - if err != nil { - logex.Fatal("wget_port form is not right need int") - os.Exit(1) - } - logex.Notice(">>> WgetPort:", wget_port) + transfer.Dict.WgetPort = configMgr.Read("default", "wget_port") + if transfer.Dict.WgetPort == "" { + logex.Fatal("ERROR: nead [default] wget_port in config_file!") + fmt.Fprintln(os.Stderr, "ERROR: nead [default] wget_port in config_file!") + fmt.Fprintln(os.Stderr, usage) + os.Exit(1) + } + var wget_port int + wget_port, err = strconv.Atoi(transfer.Dict.WgetPort) + if err != nil { + logex.Fatal("wget_port form is not right need int") + os.Exit(1) + } + logex.Notice(">>> WgetPort:", wget_port) transfer.BuildToolLocal = configMgr.Read("default", "buildtool_local") if transfer.BuildToolLocal == "" { + logex.Fatal("ERROR: nead [default] BuildToolLocal in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] BuildToolLocal in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -127,6 +134,7 @@ Log options: transfer.Dict.DonefileAddress = configMgr.Read("default", "donefile_address") if transfer.Dict.DonefileAddress == "" { + logex.Fatal("ERROR: nead [default] DonefileAddress in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] DonefileAddress in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -135,6 +143,7 @@ Log options: transfer.Dict.OutputAddress = configMgr.Read("default", "output_address") if transfer.Dict.OutputAddress == "" { + logex.Fatal("ERROR: nead [default] OutputAddress in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] OutputAddress in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -143,6 +152,7 @@ Log options: transfer.Dict.TmpAddress = configMgr.Read("default", "tmp_address") if transfer.Dict.TmpAddress == "" { + logex.Fatal("ERROR: nead [default] TmpAddress in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] TmpAddress in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -151,6 +161,7 @@ Log options: ShardNumStr := configMgr.Read("default", "shard_num") if ShardNumStr == "" { + logex.Fatal("ERROR: nead [default] ShardNum in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] ShardNum in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -164,6 +175,7 @@ Log options: CopyNumStr := configMgr.Read("default", "copy_num") if CopyNumStr == "" { + logex.Fatal("ERROR: nead [default] CopyNum in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] CopyNum in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -179,6 +191,7 @@ Log options: transfer.Dict.DeployPath = configMgr.Read("default", "deploy_path") if transfer.Dict.DeployPath == "" { + logex.Fatal("ERROR: nead [default] DeployPath in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] DeployPath in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -187,6 +200,7 @@ Log options: transfer.TransferAddr = configMgr.Read("default", "transfer_address") if transfer.TransferAddr == "" { + logex.Fatal("ERROR: nead [default] TransferAddr in config_file!") fmt.Fprintln(os.Stderr, "ERROR: nead [default] TransferAddr in config_file!") fmt.Fprintln(os.Stderr, usage) os.Exit(1) @@ -199,9 +213,17 @@ Log options: agentName := fmt.Sprintf("agent%d_%d", i, j) agentInfo := configMgr.Read("cube_agent", agentName) agentInfoSlice := strings.Split(agentInfo, ":") + if len(agentInfoSlice) != 2 { + logex.Fatal("agent conf format not right! sample: ip:port") + os.Exit(1) + } cubeName := fmt.Sprintf("cube%d_%d", i, j) cubeInfo := configMgr.Read("cube_agent", cubeName) cubeInfoSlice := strings.Split(cubeInfo, ":") + if len(cubeInfoSlice) != 3 { + logex.Fatal("cube conf format not right! sample: ip:port:deploy_path") + os.Exit(1) + } instance.DictName = transfer.Dict.DictName instance.AgentIp = agentInfoSlice[0] instance.AgentPort, _ = strconv.Atoi(agentInfoSlice[1]) diff --git a/cube/cube-transfer/src/transfer/trigger.go b/cube/cube-transfer/src/transfer/trigger.go index c962726d20c1fee7bbbaa282000640df1c8036f6..b3696dc58b7ca33de307cbe7ea2d4509d269753c 100755 --- a/cube/cube-transfer/src/transfer/trigger.go +++ b/cube/cube-transfer/src/transfer/trigger.go @@ -52,7 +52,16 @@ func GetDoneFileInfo(addr string) (version dict.DictVersionInfo, err error) { } else { contentss := string(contents) lines := strings.Split(contentss, "\n") - index := len(lines) - 2 + index := len(lines) - 1 + //one line length smaller than 3 maybe blank or return + for len(lines[index]) < 3 { + index-- + } + if index < 0 { + logex.Noticef("[trigrer]get base donfile info error") + err = fmt.Errorf("[trigrer]get base donfile info error") + return + } var donefileInfo dict.DonefileInfo fmt.Printf("line %v: %v\n", index, lines[index]) if err = json.Unmarshal([]byte(lines[index]), &donefileInfo); err != nil { @@ -83,7 +92,13 @@ func GetDoneFileInfo(addr string) (version dict.DictVersionInfo, err error) { } else { contentss := string(contents) lines := strings.Split(contentss, "\n") + for index := 0; index < len(lines)-1; index++ { + if len(lines[index]) < 3 { + logex.Noticef("[trigrer]get patch donfile info error") + err = fmt.Errorf("[trigrer]get patch donfile info error") + return + } var donefileInfo dict.DonefileInfo if err = json.Unmarshal([]byte(lines[index]), &donefileInfo); err != nil { return diff --git a/demo-client/CMakeLists.txt b/demo-client/CMakeLists.txt index 72847a8990222398597544de8ba5dc10521e4c71..897b627874bdfd41e89c5ac3c111addd0fc9567c 100644 --- a/demo-client/CMakeLists.txt +++ b/demo-client/CMakeLists.txt @@ -77,6 +77,10 @@ install(TARGETS echo RUNTIME DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/echo/bin) install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/conf DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/echo/) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/python/echo.py + DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/echo/python) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/php/echo.php + DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/echo/php) install(TARGETS echo_kvdb RUNTIME DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/echo_kvdb/bin) @@ -108,6 +112,14 @@ install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/conf DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/text_classification/) install(DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/data/text_classification DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/client/text_classification/data) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/python/text_classification.py + DESTINATION + ${PADDLE_SERVING_INSTALL_DIR}/demo/client/text_classification/python) +install(FILES ${CMAKE_CURRENT_LIST_DIR}/php/text_classification.php + DESTINATION + ${PADDLE_SERVING_INSTALL_DIR}/demo/client/text_classification/php) + + install(TARGETS ctr_prediction RUNTIME DESTINATION diff --git a/demo-client/php/echo.php b/demo-client/php/echo.php new file mode 100644 index 0000000000000000000000000000000000000000..cfc50a4e2ef837c1899043abd14798b935fb43cb --- /dev/null +++ b/demo-client/php/echo.php @@ -0,0 +1,40 @@ +value ,key1 => value1 ,.... +echo http_post('http://127.0.0.1:8010/BuiltinTestEchoService/inference', array("a" => 1, "b" => 0.5)); +?> diff --git a/demo-client/php/text_classification.php b/demo-client/php/text_classification.php new file mode 100644 index 0000000000000000000000000000000000000000..f924135e284bd6807ff648944d3447635409bd73 --- /dev/null +++ b/demo-client/php/text_classification.php @@ -0,0 +1,113 @@ + $sample); + $labels[] = $label; + + unset($x); + unset($buffer); + unset($ids); + unset($sample); + unset($label); + } + + if (!feof($handle)) { + echo "Unexpected fgets() fail"; + return -1; + } + fclose($handle); +} + +function &http_connect($url) { + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST"); + // true是获取文本,不直接输出 + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + // 强制curl不使用100-continue + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + // set header + curl_setopt($ch, + CURLOPT_HTTPHEADER, + array( + 'Content-Type: application/json' + ) + ); + + return $ch; +} + +function http_post(&$ch, $data) { + // array to json string + $data_string = json_encode($data); + + // post data 封装 + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + + // set header + curl_setopt($ch, + CURLOPT_HTTPHEADER, + array( + 'Content-Length: ' . strlen($data_string) + ) + ); + + // 执行 + $result = curl_exec($ch); + return $result; +} + +if ($argc != 2) { + echo "Usage: php text_classification.php DATA_SET_FILE\n"; + return -1; +} + +ini_set('memory_limit', '-1'); + +$samples = array(); +$labels = array(); +read_data($argv[1], $samples, $labels); +echo count($samples) . "\n"; + +$ch = &http_connect('http://127.0.0.1:8010/TextClassificationService/inference'); + +$count = 0; +for ($i = 0; $i < count($samples) - BATCH_SIZE; $i += BATCH_SIZE) { + $instances = array_slice($samples, $i, BATCH_SIZE); + echo http_post($ch, array("instances" => $instances)) . "\n"; +} + +curl_close($ch); + +?> diff --git a/demo-client/python/echo.py b/demo-client/python/echo.py new file mode 100644 index 0000000000000000000000000000000000000000..5f447e6dd8b70a0110ebd623d2a0c94680bd42ed --- /dev/null +++ b/demo-client/python/echo.py @@ -0,0 +1,26 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import urllib2 + +data = {"a": 1, "b": 0.5} +request_json = json.dumps(data) + +req = urllib2.Request("http://127.0.0.1:8010/BuiltinTestEchoService/inference") +try: + response = urllib2.urlopen(req, request_json, 1) + print response.read() +except urllib2.HTTPError as e: + print e.reason diff --git a/demo-client/python/text_classification.py b/demo-client/python/text_classification.py new file mode 100644 index 0000000000000000000000000000000000000000..3f1bf76e13c501646fc4ad1e535327bc47500a05 --- /dev/null +++ b/demo-client/python/text_classification.py @@ -0,0 +1,70 @@ +# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import httplib +import sys +import os + +BATCH_SIZE = 10 + + +def data_reader(data_file, samples, labels): + if not os.path.exists(data_file): + print "Path %s not exist" % data_file + return -1 + + with open(data_file, "r") as f: + for line in f: + line = line.replace('(', ' ') + line = line.replace(')', ' ') + line = line.replace('[', ' ') + line = line.replace(']', ' ') + ids = line.split(',') + ids = [int(x) for x in ids] + label = ids[-1] + ids = ids[0:-1] + samples.append(ids) + labels.append(label) + + +if __name__ == "__main__": + """ main + """ + if len(sys.argv) != 2: + print "Usage: python text_classification.py DATA_FILE" + sys.exit(-1) + + samples = [] + labels = [] + ret = data_reader(sys.argv[1], samples, labels) + + conn = httplib.HTTPConnection("127.0.0.1", 8010) + + for i in range(0, len(samples) - BATCH_SIZE, BATCH_SIZE): + batch = samples[i:i + BATCH_SIZE] + ids = [] + for x in batch: + ids.append({"ids": x}) + ids = {"instances": ids} + + request_json = json.dumps(ids) + + try: + conn.request('POST', "/TextClassificationService/inference", + request_json, {"Content-Type": "application/json"}) + response = conn.getresponse() + print response.read() + except httplib.HTTPException as e: + print e.reason diff --git a/demo-serving/CMakeLists.txt b/demo-serving/CMakeLists.txt index dcb6e3105583c9e299295afaf5f521f58541d613..6491827958ccb014bd22e369b0d8b7a834211d3a 100644 --- a/demo-serving/CMakeLists.txt +++ b/demo-serving/CMakeLists.txt @@ -25,7 +25,12 @@ if (NOT EXISTS endif() include_directories(SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../kvdb/include) -find_library(MKLML_LIBS NAMES libmklml_intel.so libiomp5.so) + +find_library(MKLML_LIB NAMES libmklml_intel.so PATHS + ${CMAKE_BINARY_DIR}/Paddle/third_party/install/mklml/lib/) +find_library(MKLML_IOMP_LIB NAMES libiomp5.so PATHS + ${CMAKE_BINARY_DIR}/Paddle/third_party/install/mklml/lib) + include(op/CMakeLists.txt) include(proto/CMakeLists.txt) add_executable(serving ${serving_srcs}) @@ -75,8 +80,7 @@ install(FILES ${inc} DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/include/serving) if (${WITH_MKL}) - install(FILES ${THIRD_PARTY_PATH}/install/mklml/lib/libmklml_intel.so - ${THIRD_PARTY_PATH}/install/mklml/lib/libmklml_gnu.so - ${THIRD_PARTY_PATH}/install/mklml/lib/libiomp5.so DESTINATION + install(FILES ${CMAKE_BINARY_DIR}/Paddle/third_party/install/mklml/lib/libmklml_intel.so + ${CMAKE_BINARY_DIR}/Paddle/third_party/install/mklml/lib/libiomp5.so DESTINATION ${PADDLE_SERVING_INSTALL_DIR}/demo/serving/bin) endif() diff --git a/doc/HTTP_INTERFACE.md b/doc/HTTP_INTERFACE.md new file mode 100644 index 0000000000000000000000000000000000000000..5be35c745010ef87caae66e60dd577f04408b167 --- /dev/null +++ b/doc/HTTP_INTERFACE.md @@ -0,0 +1,131 @@ +# HTTP Inferface + +Paddle Serving服务均可以通过HTTP接口访问,客户端只需按照Service定义的Request消息格式构造json字符串即可。客户端构造HTTP请求,将json格式数据以POST请求发给serving端,serving端**自动**按Service定义的Protobuf消息格式,将json数据转换成protobuf消息。 + +本文档介绍以python和PHP语言访问Serving的HTTP服务接口的用法。 + +## 1. 访问地址 + +访问Serving节点的HTTP服务与C++服务使用同一个端口(例如8010),访问URL规则为: + +``` +http://127.0.0.1:8010/ServiceName/inference +http://127.0.0.1:8010/ServiceName/debug +``` + +其中ServiceName应该与Serving的配置文件`conf/services.prototxt`中配置的一致,假如有如下2个service: + +```protobuf +services { + name: "BuiltinTestEchoService" + workflows: "workflow3" +} + +services { + name: "TextClassificationService" + workflows: "workflow6" +} +``` + +则访问上述2个Serving服务的HTTP URL分别为: + +``` +http://127.0.0.1:8010/BuiltinTestEchoService/inference +http://127.0.0.1:8010/BuiltinTestEchoService/debug + +http://127.0.0.1:8010/TextClassificationService/inference +http://127.0.0.1:8010/TextClassificationService/debug +``` + +## 2. Python访问HTTP Serving + +Python语言访问HTTP Serving,关键在于构造json格式的请求数据,可以通过以下步骤完成: + +1) 按照Service定义的Request消息格式构造python object +2) `json.dump()` / `json.dumps()` 等函数将python object转换成json格式字符串 + +以TextClassificationService为例,关键代码如下: + +```python +# Connect to server +conn = httplib.HTTPConnection("127.0.0.1", 8010) + +# samples是一个list,其中每个元素是一个ids字典: +# samples[0] = [190, 1, 70, 382, 914, 5146, 190...] +for i in range(0, len(samples) - BATCH_SIZE, BATCH_SIZE): + # 构建批量预测数据 + batch = samples[i: i + BATCH_SIZE] + ids = [] + for x in batch: + ids.append({"ids" : x}) + ids = {"instances": ids} + + # python object转成json + request_json = json.dumps(ids) + + # 请求HTTP服务,打印response + try: + conn.request('POST', "/TextClassificationService/inference", request_json, {"Content-Type": "application/json"}) + response = conn.getresponse() + print response.read() + except httplib.HTTPException as e: + print e.reason +``` + +完整示例请参考[text_classification.py](../demo-client/python/text_classification.py) + +## 3. PHP访问HTTP Serving + +PHP语言构造json格式字符串的步骤如下: + +1) 按照Service定义的Request消息格式,构造PHP array +2) `json_encode()`函数将PHP array转换成json字符串 + +以TextCLassificationService为例,关键代码如下: + +```PHP +function http_post(&$ch, $data) { + // array to json string + $data_string = json_encode($data); + + // post data 封装 + curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); + + // set header + curl_setopt($ch, + CURLOPT_HTTPHEADER, + array( + 'Content-Length: ' . strlen($data_string) + ) + ); + + // 执行 + $result = curl_exec($ch); + return $result; +} + +$ch = &http_connect('http://127.0.0.1:8010/TextClassificationService/inference'); + +$count = 0; + +# $samples是一个2层array,其中每个元素是一个如下array: +# $samples[0] = array( +# "ids" => array( +# [0] => int(190), +# [1] => int(1), +# [2] => int(70), +# [3] => int(382), +# [4] => int(914), +# [5] => int(5146), +# [6] => int(190)...) +# ) + +for ($i = 0; $i < count($samples) - BATCH_SIZE; $i += BATCH_SIZE) { + $instances = array_slice($samples, $i, BATCH_SIZE); + echo http_post($ch, array("instances" => $instances)) . "\n"; +} + +curl_close($ch); +``` + +完整代码请参考[text_classification.php](../demo-client/php/text_classification.php)