pax_global_header00006660000000000000000000000064152521742440014520gustar00rootroot0000000000000052 comment=ac65014bd886e024499a900f314c6592382e4aa6 sogou-srpc-ac65014/000077500000000000000000000000001525217424400141465ustar00rootroot00000000000000sogou-srpc-ac65014/.bazelignore000066400000000000000000000001271525217424400164500ustar00rootroot00000000000000./third_party/snappy/third_party/benchmark ./third_party/snappy/third_party/googletest sogou-srpc-ac65014/.editorconfig000066400000000000000000000001361525217424400166230ustar00rootroot00000000000000# top-most EditorConfig file root = true # all files [*] indent_style = tab indent_size = 4 sogou-srpc-ac65014/.github/000077500000000000000000000000001525217424400155065ustar00rootroot00000000000000sogou-srpc-ac65014/.github/workflows/000077500000000000000000000000001525217424400175435ustar00rootroot00000000000000sogou-srpc-ac65014/.github/workflows/ci.yml000066400000000000000000000042661525217424400206710ustar00rootroot00000000000000name: ci build on: push: branches: [ master ] pull_request: branches: [ master ] jobs: ubuntu-cmake: name: ubuntu runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: install deps run: | sudo apt-get update sudo apt-get install -y libprotobuf-dev protobuf-compiler libgtest-dev valgrind - name: update submodules run: git submodule update --init --recursive - name: make run: make -j4 - name: make tutorial run: make tutorial -j4 - name: make check run: make check -j4 - name: make install run: sudo make install fedora-cmake: name: fedora runs-on: ubuntu-latest steps: - name: Setup Podman run: | sudo apt update sudo apt-get -y install podman podman pull fedora:rawhide - name: Get source uses: actions/checkout@master with: path: 'workflow' - name: Create container and run tests run: | { echo 'FROM fedora:rawhide' echo 'RUN dnf -y update' echo 'RUN dnf -y install cmake gcc-c++ gtest-devel git make' echo 'RUN dnf -y install openssl-devel protobuf-devel' echo 'RUN dnf -y install lz4-devel snappy-devel' echo 'RUN dnf clean all' echo 'COPY workflow workflow' echo 'WORKDIR /workflow' echo 'RUN git submodule update --init --recursive' echo 'RUN cmake' echo 'RUN make' echo 'RUN make check' echo 'RUN make tutorial' } > podmanfile podman build --tag fedorarawhide -f ./podmanfile ubuntu-bazel: name: bazel runs-on: ubuntu-latest steps: - uses: actions/checkout@master - name: Install Bazel 5.4.0 run: | sudo apt-get update sudo apt-get install -y apt-transport-https curl gnupg curl -LO "https://releases.bazel.build/5.4.0/release/bazel-5.4.0-linux-x86_64" chmod +x bazel-5.4.0-linux-x86_64 sudo mv bazel-5.4.0-linux-x86_64 /usr/local/bin/bazel - name: bazel build run: bazel build ... sogou-srpc-ac65014/.gitignore000066400000000000000000000005431525217424400161400ustar00rootroot00000000000000*.a *.bak *.gz *.zip *.tar *.la *.lo *.o *.rpm *.so *.so.* *.cmake *.vcxproj *.filters *.sln *.pb.h *.pb.cc *.log *.srpc.h *.thrift.h *.pb_skeleton.h *.pb_skeleton.cc *.thrift_skeleton.h *.thrift_skeleton.cc _bin/ _include/ _lib/ .deps/ build/ build_pkg/ CMakeFiles/ Debug/ Release/ missing SRCINFO SRCNUMVER SRCVERSION CMakeCache.txt Makefile bazel-* sogou-srpc-ac65014/.gitmodules000066400000000000000000000004621525217424400163250ustar00rootroot00000000000000[submodule "workflow"] path = workflow url = https://github.com/sogou/workflow.git [submodule "third_party/snappy"] path = third_party/snappy url = https://github.com/google/snappy branch = 1.1.9 [submodule "third_party/lz4"] path = third_party/lz4 url = https://github.com/lz4/lz4 branch = v1.9.3 sogou-srpc-ac65014/BUILD000066400000000000000000000162331525217424400147350ustar00rootroot00000000000000load("@rules_cc//cc:defs.bzl", "cc_proto_library") load("@rules_proto//proto:defs.bzl", "proto_library") load(":srpc.bzl", "srpc_cc_library") proto_library( name = "message_proto", srcs = [ "src/message/rpc_meta.proto", "src/message/rpc_meta_brpc.proto", "src/message/rpc_meta_trpc.proto", ], strip_import_prefix = "src/message", ) cc_proto_library( name = "MessageProto", deps = [":message_proto"], ) proto_library( name = "module_proto", srcs = [ "src/module/proto/opentelemetry_common.proto", "src/module/proto/opentelemetry_resource.proto", "src/module/proto/opentelemetry_trace.proto", "src/module/proto/opentelemetry_metrics.proto", "src/module/proto/opentelemetry_metrics_service.proto", ], strip_import_prefix = "src/module/proto", ) cc_proto_library( name = "ModuleProto", deps = [":module_proto"], ) cc_library( name = "srpc_hdrs", hdrs = glob(["src/include/srpc/*"]), includes = ["src/include"], visibility = ["//visibility:public"], deps = [ "@workflow//:workflow_hdrs", ], ) cc_library( name = "srpc", srcs = glob(["src/**/*.cc"]), hdrs = glob([ "src/**/*.h", "src/**/*.inl", ]), includes = [ "src", "src/compress", "src/message", "src/module", "src/thrift", "src/var", ], visibility = ["//visibility:public"], deps = [ ":MessageProto", ":ModuleProto", "@lz4", "@snappy", "@workflow//:http", "@workflow//:redis", "@workflow//:upstream", ], ) cc_library( name = "srpc_generator_lib", srcs = glob( [ "src/generator/*.cc", ], exclude = [ "src/compiler.cc", ], ), hdrs = glob([ "src/generator/*.h", ]), includes = ["src/generator"], visibility = ["//visibility:public"], deps = [ ":srpc", ], ) cc_binary( name = "srpc_generator", srcs = ["src/generator/compiler.cc"], visibility = ["//visibility:public"], deps = [ ":srpc", ":srpc_generator_lib", ], ) proto_library( name = "echo_pb_proto", srcs = [ "tutorial/echo_pb.proto", ], strip_import_prefix = "tutorial", ) cc_proto_library( name = "EchoProto", deps = [":echo_pb_proto"], ) srpc_cc_library( name = "echo_pb_srpc", srcs = ["tutorial/echo_pb.proto"], deps = [":EchoProto"], ) cc_binary( name = "srpc_pb_server", srcs = ["tutorial/tutorial-01-srpc_pb_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "srpc_pb_client", srcs = ["tutorial/tutorial-02-srpc_pb_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) srpc_cc_library( name = "echo_thrift_srpc", srcs = ["tutorial/echo_thrift.thrift"], type = "thrift", ) cc_binary( name = "srpc_thrift_server", srcs = ["tutorial/tutorial-03-srpc_thrift_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_thrift_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "srpc_thrift_client", srcs = ["tutorial/tutorial-04-srpc_thrift_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_thrift_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "brpc_pb_server", srcs = ["tutorial/tutorial-05-brpc_pb_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "brpc_pb_client", srcs = ["tutorial/tutorial-06-brpc_pb_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "thrift_thrift_server", srcs = ["tutorial/tutorial-07-thrift_thrift_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_thrift_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "thrift_thrift_client", srcs = ["tutorial/tutorial-08-thrift_thrift_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_thrift_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "client_task", srcs = ["tutorial/tutorial-09-client_task.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "server_async", srcs = ["tutorial/tutorial-10-server_async.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) proto_library( name = "helloworld_proto", srcs = [ "tutorial/helloworld.proto", ], strip_import_prefix = "tutorial", ) cc_proto_library( name = "HelloworldProto", deps = [":helloworld_proto"], ) srpc_cc_library( name = "helloworld_pb_srpc", srcs = ["tutorial/helloworld.proto"], deps = [":HelloworldProto"], ) cc_binary( name = "trpc_pb_server", srcs = ["tutorial/tutorial-11-trpc_pb_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":helloworld_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "trpc_pb_client", srcs = ["tutorial/tutorial-12-trpc_pb_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":helloworld_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "trpc_http_server", srcs = ["tutorial/tutorial-13-trpc_http_server.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":helloworld_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "trpc_http_client", srcs = ["tutorial/tutorial-14-trpc_http_client.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":helloworld_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "srpc_pb_proxy", srcs = ["tutorial/tutorial-15-srpc_pb_proxy.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) cc_binary( name = "server_with_metrics", srcs = ["tutorial/tutorial-16-server_with_metrics.cc"], linkopts = [ "-lpthread", "-lssl", "-lcrypto", ], deps = [ ":echo_pb_srpc", ":srpc", ":srpc_hdrs", ], ) sogou-srpc-ac65014/CMakeLists.txt000066400000000000000000000132071525217424400167110ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type") set(CMAKE_SKIP_RPATH TRUE) project(srpc VERSION 0.10.5 LANGUAGES C CXX) ###Options if (WIN32) option(SRPC_BUILD_STATIC_RUNTIME "Use static runtime" ON) endif () #### CHECK include(CheckIncludeFile) include(CheckIncludeFileCXX) set(THIRD_PARTY_FATAL_MESSAGE " is neither installed nor found in third_party! Sugguestion to initial third_party: \"git submodule update --init\"" ) find_library(LZ4_LIBRARY NAMES lz4) check_include_file("lz4.h" LZ4_INSTALLED) if (NOT LZ4_INSTALLED AND ${LZ4_LIBRARY} STREQUAL "LZ4_LIBRARY-NOTFOUND") if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/lz4/lib/lz4.h") message( FATAL_ERROR "\nLz4" ${THIRD_PARTY_FATAL_MESSAGE} ) endif () else () if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/lz4/lib/lz4.h") message("Lz4 third_party FOUND. Use for source code dependencies.") set(LZ4_INSTALLED 0 CACHE INTERNAL "check_lz4_installed") else () find_path(LZ4_INCLUDE_DIR NAMES "lz4.h") include_directories(${LZ4_INCLUDE_DIR}) set(LZ4_INSTALLED 1 CACHE INTERNAL "check_lz4_installed") endif () endif () find_package(Snappy) check_include_file_cxx("snappy.h" SNAPPY_INSTALLED) if (NOT SNAPPY_INSTALLED AND NOT ${Snappy_FOUND}) if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/snappy/cmake") message( FATAL_ERROR "\nSnappy" ${THIRD_PARTY_FATAL_MESSAGE} ) endif () else () if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/third_party/snappy/cmake") message("Snappy third_party FOUND. Use for source code dependencies.") set(SNAPPY_INSTALLED 0 CACHE INTERNAL "check_snappy_installed") else () find_path(Snappy_INCLUDE_DIR NAMES "snappy.h") include_directories(${Snappy_INCLUDE_DIR}) set(SNAPPY_INSTALLED 1 CACHE INTERNAL "check_snappy_installed") endif () endif () check_include_file_cxx("workflow/Workflow.h" WORKFLOW_INSTALLED) if (NOT WORKFLOW_INSTALLED) if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in") message( FATAL_ERROR "\nWorkflow" ${THIRD_PARTY_FATAL_MESSAGE} ) endif () else () if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in") message("Workflow third_party FOUND. Use for source code dependencies.") set(WORKFLOW_INSTALLED 0) endif () endif () find_program(PROTOC "protoc") if(${PROTOC} STREQUAL "PROTOC-NOTFOUND") message( FATAL_ERROR "Protobuf compiler is missing!") endif() #### PREPARE set(INC_DIR ${PROJECT_SOURCE_DIR}/_include CACHE PATH "srpc inc") set(LIB_DIR ${PROJECT_SOURCE_DIR}/_lib CACHE PATH "srpc lib") set(BIN_DIR ${PROJECT_SOURCE_DIR}/_bin CACHE PATH "srpc bin") include(GNUInstallDirs) set(CMAKE_CONFIG_INSTALL_FILE ${PROJECT_BINARY_DIR}/config.toinstall.cmake) set(CMAKE_CONFIG_INSTALL_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIB_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR}) add_custom_target( LINK_HEADERS ALL COMMENT "link headers..." ) INCLUDE(CMakeLists_Headers.txt) macro(makeLink src dest target) add_custom_command( TARGET ${target} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dest} DEPENDS ${dest} ) endmacro() add_custom_command( TARGET LINK_HEADERS PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${INC_DIR}/${PROJECT_NAME} ) foreach(header_file ${INCLUDE_HEADERS}) string(REPLACE "/" ";" arr ${header_file}) list(GET arr -1 file_name) makeLink(${PROJECT_SOURCE_DIR}/${header_file} ${INC_DIR}/${PROJECT_NAME}/${file_name} LINK_HEADERS) endforeach() if (WIN32) if (SRPC_BUILD_STATIC_RUNTIME) set(CompilerFlags CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL ) foreach(CompilerFlag ${CompilerFlags}) string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") endforeach () endif () endif () add_subdirectory(src) #### CONFIG include(CMakePackageConfigHelpers) set(CONFIG_INC_DIR ${INC_DIR}) set(CONFIG_LIB_DIR ${LIB_DIR}) if (VCPKG_TOOLCHAIN AND EXISTS "${CMAKE_INSTALL_PREFIX}/tools/srpc") set(CONFIG_BIN_DIR ${CMAKE_INSTALL_PREFIX}/tools/srpc) set(WITH_VCPKG_TOOLCHAIN 1 CACHE INTERNAL "build_with_vcpkg_toolchain") message("Install with VCPKG toolchain. Dir ${CMAKE_INSTALL_PREFIX}/tools/srpc.") else() set(CONFIG_BIN_DIR ${BIN_DIR}) set(WITH_VCPKG_TOOLCHAIN 0 CACHE INTERNAL "build_with_vcpkg_toolchain") endif() configure_package_config_file( ${PROJECT_NAME}-config.cmake.in ${PROJECT_SOURCE_DIR}/${PROJECT_NAME}-config.cmake INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} PATH_VARS CONFIG_INC_DIR CONFIG_LIB_DIR CONFIG_BIN_DIR ) set(CONFIG_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}) set(CONFIG_LIB_DIR ${CMAKE_INSTALL_LIBDIR}) set(CONFIG_BIN_DIR ${CMAKE_INSTALL_BINDIR}) configure_package_config_file( ${PROJECT_NAME}-config.cmake.in ${CMAKE_CONFIG_INSTALL_FILE} INSTALL_DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} PATH_VARS CONFIG_INC_DIR CONFIG_LIB_DIR CONFIG_BIN_DIR ) write_basic_package_version_file( ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake VERSION ${WORKFLOW_VERSION} COMPATIBILITY AnyNewerVersion ) install( FILES ${CMAKE_CONFIG_INSTALL_FILE} DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} COMPONENT devel RENAME ${PROJECT_NAME}-config.cmake ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake DESTINATION ${CMAKE_CONFIG_INSTALL_DIR} COMPONENT devel ) install( FILES ${INCLUDE_HEADERS} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME} COMPONENT devel ) install( FILES README.md DESTINATION "${CMAKE_INSTALL_DOCDIR}-${PROJECT_VERSION}" COMPONENT devel ) sogou-srpc-ac65014/CMakeLists_Headers.txt000066400000000000000000000027721525217424400203510ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(SRC_HEADERS src/compress/rpc_compress.h src/compress/rpc_compress_gzip.h src/message/rpc_message.h src/message/rpc_message_srpc.h src/message/rpc_message_thrift.h src/message/rpc_message_brpc.h src/message/rpc_message_trpc.h src/thrift/rpc_thrift_buffer.h src/thrift/rpc_thrift_enum.h src/thrift/rpc_thrift_idl.h src/thrift/rpc_thrift_idl.inl src/var/ckms_quantiles.h src/var/time_window_quantiles.h src/var/rpc_var.h src/module/rpc_module.h src/module/rpc_trace_module.h src/module/rpc_metrics_module.h src/module/rpc_filter.h src/module/rpc_trace_filter.h src/module/rpc_metrics_filter.h src/rpc_basic.h src/rpc_buffer.h src/rpc_client.h src/rpc_context.h src/rpc_context.inl src/rpc_global.h src/rpc_options.h src/rpc_server.h src/rpc_service.h src/rpc_task.inl src/rpc_types.h src/rpc_zero_copy_stream.h src/rpc_define.h ) if (NOT WIN32) set(SRC_HEADERS ${SRC_HEADERS} src/http/http_task.h src/http/http_module.h src/http/http_client.h src/http/http_server.h ) endif () if (NOT SNAPPY_INSTALLED) set(SNAPPY_HEADERS third_party/snappy/snappy.h third_party/snappy/snappy-c.h third_party/snappy/snappy-sinksource.h third_party/snappy/snappy-stubs-public.h ) endif () if (NOT LZ4_INSTALLED) set(LZ4_HEADERS third_party/lz4/lib/lz4.h third_party/lz4/lib/lz4frame.h ) endif () if (WITH_VCPKG_TOOLCHAIN) set(INCLUDE_HEADERS ${SRC_HEADERS}) else() set(INCLUDE_HEADERS ${SRC_HEADERS} ${SNAPPY_HEADERS} ${LZ4_HEADERS}) endif() sogou-srpc-ac65014/CODE_OF_CONDUCT.md000066400000000000000000000062571525217424400167570ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others’ private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at liyingxin@sogou-inc.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ sogou-srpc-ac65014/GNUmakefile000066400000000000000000000032531525217424400162230ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) ALL_TARGETS := all base check install preinstall package rpm clean tutorial example MAKE_FILE := Makefile DEFAULT_BUILD_DIR := build.cmake BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi) CMAKE3 := $(shell if which cmake3>/dev/null ; then echo cmake3; else echo cmake; fi;) WORKFLOW := $(shell if [ -f "workflow/workflow-config.cmake.in" ]; then echo "Found"; else echo "NotFound"; fi) .PHONY: $(ALL_TARGETS) all: base make -C $(BUILD_DIR) -f Makefile base: ifeq ("$(WORKFLOW)","Found") make -C workflow endif mkdir -p $(BUILD_DIR) ifeq ($(DEBUG),y) cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR) else ifneq ("${INSTALL_PREFIX}install_prefix", "install_prefix") cd $(BUILD_DIR) && $(CMAKE3) -DCMAKE_INSTALL_PREFIX:STRING=${INSTALL_PREFIX} $(ROOT_DIR) else cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) endif tutorial: all make -C tutorial check: all make -C test check install preinstall package: base mkdir -p $(BUILD_DIR) cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) make -C $(BUILD_DIR) -f Makefile $@ rpm: package ifneq ($(BUILD_DIR),.) mv $(BUILD_DIR)/*.rpm ./ endif clean: ifeq ("$(WORKFLOW)","Found") -make -C workflow clean endif -make -C test clean -make -C tutorial clean -make -C benchmark clean rm -rf $(DEFAULT_BUILD_DIR) rm -rf _include rm -rf _lib rm -rf _bin rm -f SRCINFO SRCNUMVER SRCVERSION rm -f ./*.rpm rm -f src/message/*.pb.h src/message/*.pb.cc find . -name CMakeCache.txt | xargs rm -f find . -name Makefile | xargs rm -f find . -name "*.cmake" | xargs rm -f find . -name CMakeFiles | xargs rm -rf sogou-srpc-ac65014/LICENSE000066400000000000000000000236761525217424400151710ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS sogou-srpc-ac65014/README.md000066400000000000000000000203711525217424400154300ustar00rootroot00000000000000[中文版入�](README_cn.md)
srpc-logo ### NEW !!! 👉 [SRPC tools : build Workflow and SRPC projects easily.](/tools/README.md) ## Introduction **SRPC is an enterprise-level RPC system used by almost all online services in Sogou. It handles tens of billions of requests every day, covering searches, recommendations, advertising system, and other types of services.** Bases on [Sogou C++ Workflow](https://github.com/sogou/workflow), it is an excellent choice for high-performance, low-latency, lightweight RPC systems. Contains AOP aspect-oriented modules that can report Metrics and Trace to a variety of cloud-native systems, such as OpenTelemetry, etc. Its main features include: * Support multiple RPC protocols: [`SRPC`](/tutorial/tutorial-01-srpc_pb_server.cc), [`bRPC`](/tutorial/tutorial-05-brpc_pb_server.cc), [`Thrift`](/tutorial/tutorial-07-thrift_thrift_server.cc), [`tRPC`](/tutorial/tutorial-11-trpc_pb_server.cc) * Support multiple operating systems: `Linux`, `MacOS`, `Windows` * Support several IDL formats: [`Protobuf`](/tutorial/echo_pb.proto), [`Thrift`](/tutorial/echo_thrift.thrift) * Support several data formats transparently: `Json`, `Protobuf`, `Thrift Binary` * Support several compression formats, the framework automatically decompresses: `gzip`, `zlib`, `snappy`, `lz4` * Support several communication protocols transparently: `tcp`, `udp`, `sctp`, `tcp ssl` * With [HTTP+JSON](/docs/docs-07-srpc-http.md), you can communicate with the client or server in any language * Use it together with [Workflow Series and Parallel](/docs/docs-06-workflow.md) to facilitate the use of calculations and other asynchronous resources * Perfectly compatible with all Workflow functions, such as name service, [upstream](docs/docs-06-workflow.md#3-upstream) and other components * Report [Tracing](/docs/docs-08-tracing.md) to [OpenTelemetry](https://opentelemetry.io) * Report [Metrics](/docs/docs-09-metrics.md) to [OpenTelemetry](https://opentelemetry.io) and [Prometheus](https://prometheus.io) * [More features...](/docs/en/rpc.md) ## Installation srpc has been packaged for Debian and Fedora. Therefore, we can install it from source code or from the package in the system. reference: [Linux, MacOS, Windows Installation and Compilation Guide](docs/en/installation.md) ## Quick Start Let's quickly learn how to use it in a few steps. For more detailed usage, please refer to [Documents](/docs) and [Tutorial](/tutorial). #### 1\. example.proto ~~~proto syntax = "proto3";// You can use either proto2 or proto3. Both are supported by srpc message EchoRequest { string message = 1; string name = 2; }; message EchoResponse { string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ #### 2\. generate code ~~~sh protoc example.proto --cpp_out=./ --proto_path=./ srpc_generator protobuf ./example.proto ./ ~~~ #### 3\. server.cc ~~~cpp #include #include #include "example.srpc.h" using namespace srpc; class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; void sig_handler(int signo) { } int main() { signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server_tcp; SRPCHttpServer server_http; ExampleServiceImpl impl; server_tcp.add_service(&impl); server_http.add_service(&impl); server_tcp.start(1412); server_http.start(8811); getchar(); // press "Enter" to end. server_http.stop(); server_tcp.stop(); return 0; } ~~~ #### 4\. client.cc ~~~cpp #include #include "example.srpc.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello, srpc!"); req.set_name("workflow"); client.Echo(&req, [](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); getchar(); // press "Enter" to end. return 0; } ~~~ #### 5\. make These compile commands are only for Linux system. On other system, complete cmake in [tutorial](/tutorial) is recommended. ~~~sh g++ -o server server.cc example.pb.cc -std=c++11 -lsrpc g++ -o client client.cc example.pb.cc -std=c++11 -lsrpc ~~~ #### 6\. run Terminal 1: ~~~sh ./server ~~~ Terminal 2: ~~~sh ./client ~~~ We can also use CURL to post Http request: ~~~sh curl 127.0.0.1:8811/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}' ~~~ Output of Terminal 1: ~~~sh get_req: message: "Hello, srpc!" name: "workflow" set_resp: message: "Hi, workflow" get_req: message: "from curl" name: "CURL" set_resp: message: "Hi, CURL" ~~~ Output of Terminal 2: ~~~sh message: "Hi, workflow" ~~~ Output of CURL: ~~~sh {"message":"Hi, CURL"} ~~~ ## Benchmark * CPU 2-chip/8-core/32-processor Intel(R) Xeon(R) CPU E5-2630 v3 @2.40GHz * Memory all 128G * 10 Gigabit Ethernet * BAIDU brpc-client in pooled (connection pool) mode #### QPS at cross-machine single client→ single server under different concurrency ~~~ Client = 1 ClientThread = 64, 128, 256, 512, 1024 RequestSize = 32 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark2.png) #### QPS at cross-machine multi-client→ single server under different client processes ~~~ Client = 1, 2, 4, 8, 16 ClientThread = 32 RequestSize = 32 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark4.png) #### QPS at same-machine single client→ single server under different concurrency ~~~ Client = 1 ClientThread = 1, 2, 4, 8, 16, 32, 64, 128, 256 RequestSize = 1024 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark3.png) #### QPS at same-machine single client→ single server under different request sizes ~~~ Client = 1 ClientThread = 100 RequestSize = 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark1.png) #### Latency CDF for fixed QPS at same-machine single client→ single server ~~~ Client = 1 ClientThread = 50 ClientQPS = 10000 RequestSize = 1024 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 Outlier = 1% ~~~ ![IMG](/docs/images/benchmark5.png) #### Latency CDF for fixed QPS at cross-machine multi-client→ single server ~~~ Client = 32 ClientThread = 16 ClientQPS = 2500 RequestSize = 512 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 Outlier = 1% ~~~ ![IMG](/docs/images/benchmark6.png) ## Contact * **Email** - **[liyingxin@sogou-inc.com](mailto:liyingxin@sogou-inc.com)** - main author * **Issue** - You are very welcome to post questions to [issues](https://github.com/sogou/srpc/issues) list. * **QQ** - Group number: ``618773193`` sogou-srpc-ac65014/README_cn.md000066400000000000000000000175461525217424400161220ustar00rootroot00000000000000[English version](README.md)   |   [Wiki:SRPC架构介ç»�](/docs/wiki.md) srpc-logo ### NEW !!! 👉 [SRPC tools : 一个帮你快速构建Workflowå’ŒSRPC项目的å°�工具.](/tools/README_cn.md) ## Introduction **SRPC是全æ�œç‹—业务线上使用的ä¼�业级RPC系统,目å‰�æ¯�天承载上百亿的请求é‡�,涵盖æ�œå¹¿æŽ¨å�Šå…¶ä»–类型业务。** 底层基于[Sogou C++ Workflow](https://github.com/sogou/workflow),是高性能ã€�低延迟ã€�è½»é‡�级RPC系统的æž�佳选择。且加入了AOPé�¢å�‘切é�¢çš„æ¨¡å�—,支æŒ�Metrics(监控指标)和Trace(链路追踪)功能,å�¯ä¸ŠæŠ¥åˆ°å¤šç§�云原生系统,包括OpenTelemetry。 主è¦�功能和示例: * 支æŒ�多ç§�RPCå��议:[`SRPC`](/tutorial/tutorial-01-srpc_pb_server.cc)ã€�[`bRPC`](/tutorial/tutorial-05-brpc_pb_server.cc)ã€�[`Thrift`](/tutorial/tutorial-07-thrift_thrift_server.cc)ã€�[`tRPC`](/tutorial/tutorial-11-trpc_pb_server.cc) * 支æŒ�多ç§�æ“�作系统:`Linux` / `MacOS` / `Windows` * 支æŒ�多ç§�IDLæ ¼å¼�:[`Protobuf`](/tutorial/echo_pb.proto)ã€�[`Thrift`](/tutorial/echo_thrift.thrift) * 支æŒ�多ç§�æ•°æ�®å¸ƒå±€ï¼Œä½¿ç”¨ä¸Šå®Œå…¨é€�明:`Json`ã€�`Protobuf`ã€�`Thrift Binary` * 支æŒ�多ç§�压缩,框架自动解压:`gzip`ã€�`zlib`ã€�`snappy`ã€�`lz4` * 支æŒ�多ç§�通信å��议:`tcp`, `udp`, `sctp`, `tcp ssl` * å�¯ä»¥é€šè¿‡[http+json实现跨语言](/docs/docs-07-srpc-http.md) * å�¯ä»¥ä½¿ç”¨[Workflow串并è�”任务æµ�](/docs/docs-06-workflow.md),打通计算å�Šå…¶ä»–异步资æº� * 完美兼容Workflow所有功能:命å��æœ�务体系ã€�[upstream](docs/docs-06-workflow.md#3-upstream)ã€�其他组件等 * 链路追踪功能:上报[Tracing](/docs/docs-08-tracing.md)到[OpenTelemetry](https://opentelemetry.io) * 监控功能:上报[Metrics](/docs/docs-09-metrics.md)到OpenTelemetryå’Œ[Prometheus](https://prometheus.io) ## Installation SRPC是Debian Linuxå’ŒFedora的自带安装包,因此å�¯ä»¥é€šè¿‡æº�ç �安装,和系统自带安装包安装。 具体å�‚考:[Linuxã€�MacOSã€�Windows安装和编译指å�—](docs/installation.md) ## Quick Start 我们通过几个步骤快速了解如何使用。 更详细的用法å�¯ä»¥å�‚考[更多文档](/docs),和[官方教程](/tutorial)。 #### 1. example.proto ~~~proto syntax = "proto3";//这里proto2å’Œproto3都å�¯ä»¥ï¼Œsrpc都支æŒ� message EchoRequest { string message = 1; string name = 2; }; message EchoResponse { string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ #### 2. generate code ~~~sh protoc example.proto --cpp_out=./ --proto_path=./ srpc_generator protobuf ./example.proto ./ ~~~ #### 3. server.cc ~~~cpp #include #include #include "example.srpc.h" using namespace srpc; class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; void sig_handler(int signo) { } int main() { signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server_tcp; SRPCHttpServer server_http; ExampleServiceImpl impl; server_tcp.add_service(&impl); server_http.add_service(&impl); server_tcp.start(1412); server_http.start(8811); getchar(); // press "Enter" to end. server_http.stop(); server_tcp.stop(); return 0; } ~~~ #### 4. client.cc ~~~cpp #include #include "example.srpc.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello, srpc!"); req.set_name("workflow"); client.Echo(&req, [](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); getchar(); // press "Enter" to end. return 0; } ~~~ #### 5. make 在Linux系统下的编译示例如下,其他平å�°å»ºè®®åˆ°[tutorial](/tutorial)目录下使用完整的cmake文件å��助解决编译ä¾�赖问题。 ~~~sh g++ -o server server.cc example.pb.cc -std=c++11 -lsrpc g++ -o client client.cc example.pb.cc -std=c++11 -lsrpc ~~~ #### 6. run 终端1: ~~~sh ./server ~~~ 终端2: ~~~sh ./client ~~~ 也å�¯ä»¥ç”¨CURLå�‘é€�http请求: ~~~sh curl 127.0.0.1:8811/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}' ~~~ 终端1输出: ~~~sh get_req: message: "Hello, srpc!" name: "workflow" set_resp: message: "Hi, workflow" get_req: message: "from curl" name: "CURL" set_resp: message: "Hi, CURL" ~~~ 终端2输出: ~~~sh message: "Hi, workflow" ~~~ CURL收到的回å¤�: ~~~sh {"message":"Hi, CURL"} ~~~ ## Benchmark * CPU 2-chip/8-core/32-processor Intel(R) Xeon(R) CPU E5-2630 v3 @2.40GHz * Memory all 128G * 10 Gigabit Ethernet * BAIDU brpc-client使用连接池pooled模å¼� #### 跨机å�•client→å�•server在ä¸�å�Œå¹¶å�‘çš„QPS ~~~ Client = 1 ClientThread = 64, 128, 256, 512, 1024 RequestSize = 32 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark2.png) #### 跨机多client→å�•server在ä¸�å�Œclient进程数的QPS ~~~ Client = 1, 2, 4, 8, 16 ClientThread = 32 RequestSize = 32 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark4.png) #### å�Œæœºå�•client→å�•server在ä¸�å�Œå¹¶å�‘下的QPS ~~~ Client = 1 ClientThread = 1, 2, 4, 8, 16, 32, 64, 128, 256 RequestSize = 1024 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark3.png) #### å�Œæœºå�•client→å�•server在ä¸�å�Œè¯·æ±‚大å°�下的QPS ~~~ Client = 1 ClientThread = 100 RequestSize = 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 ~~~ ![IMG](/docs/images/benchmark1.png) #### å�Œæœºå�•client→å�•server在固定QPS下的延时CDF ~~~ Client = 1 ClientThread = 50 ClientQPS = 10000 RequestSize = 1024 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 Outiler = 1% ~~~ ![IMG](/docs/images/benchmark5.png) #### 跨机多client→å�•server在固定QPS下的延时CDF ~~~ Client = 32 ClientThread = 16 ClientQPS = 2500 RequestSize = 512 Duration = 20s Server = 1 ServerIOThread = 16 ServerHandlerThread = 16 Outiler = 1% ~~~ ![IMG](/docs/images/benchmark6.png) ## 与我们è�”ç³» * **Email** - **[liyingxin@sogou-inc.com](mailto:liyingxin@sogou-inc.com)** - 主è¦�作者 * **Issue** - 使用中的任何问题都欢迎到[issues](https://github.com/sogou/srpc/issues)进行交æµ�。 * **QQ** - 群å�·: ``618773193`` sogou-srpc-ac65014/WORKSPACE000066400000000000000000000022371525217424400154330ustar00rootroot00000000000000workspace(name = "srpc") load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") http_archive( name = "rules_proto", sha256 = "d8992e6eeec276d49f1d4e63cfa05bbed6d4a26cfe6ca63c972827a0d141ea3b", strip_prefix = "rules_proto-cfdc2fa31879c0aebe31ce7702b1a9c8a4be02d2", urls = [ "https://github.com/bazelbuild/rules_proto/archive/cfdc2fa31879c0aebe31ce7702b1a9c8a4be02d2.tar.gz", ], ) load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains") rules_proto_dependencies() rules_proto_toolchains() git_repository( name = "workflow", commit = "3d5739960ac34d6164d5fec0abf268e83d72fe93", remote = "https://github.com/sogou/workflow.git") new_git_repository( name = "lz4", build_file = "@//third_party:lz4.BUILD", tag = "v1.9.3", remote = "https://github.com/lz4/lz4.git") new_git_repository( name = "snappy", build_file = "@//third_party:snappy.BUILD", tag = "1.1.9", remote = "https://github.com/google/snappy.git") sogou-srpc-ac65014/benchmark/000077500000000000000000000000001525217424400161005ustar00rootroot00000000000000sogou-srpc-ac65014/benchmark/CMakeLists.txt000066400000000000000000000070311525217424400206410ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type") project(srpc_benchmark LANGUAGES C CXX ) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) if (NOT "$ENV{LIBRARY_PATH}" STREQUAL "") string(REPLACE ":" ";" LIBRARY_PATH $ENV{LIBRARY_PATH}) set(CMAKE_SYSTEM_LIBRARY_PATH ${LIBRARY_PATH};${CMAKE_SYSTEM_LIBRARY_PATH}) endif () if (NOT "$ENV{CPLUS_INCLUDE_PATH}" STREQUAL "") string(REPLACE ":" ";" INCLUDE_PATH $ENV{CPLUS_INCLUDE_PATH}) set(CMAKE_SYSTEM_INCLUDE_PATH ${INCLUDE_PATH};${CMAKE_SYSTEM_INCLUDE_PATH}) endif () find_package(OpenSSL REQUIRED) set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "") if (WIN32) find_package(Protobuf CONFIG REQUIRED) find_library(LZ4_LIBRARY NAMES lz4) find_package(Snappy CONFIG REQUIRED) else () find_package(Protobuf REQUIRED) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/lz4/lib/lz4.h") set(LZ4_LIB lz4) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/snappy/cmake") set(SNAPPY_LIB snappy) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in") find_package(Workflow REQUIRED CONFIG HINTS ../workflow) endif () find_package(srpc REQUIRED CONFIG HINTS ..) include_directories( ${OPENSSL_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Protobuf_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR} ${SRPC_INCLUDE_DIR} ) if (WIN32) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/Debug/srpc_generator.exe) else () get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/srpc_generator) endif () protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS benchmark_pb.proto) add_custom_target( BENCHMARK_GEN ALL COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/benchmark_pb.proto ${PROJECT_SOURCE_DIR} COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/benchmark_thrift.thrift ${PROJECT_SOURCE_DIR} COMMENT "srpc generator..." ) if (WIN32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /Zc:__cplusplus /std:c++17") else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++17 -fno-exceptions") endif () if (APPLE) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES}) find_library(Workflow_LIB workflow HINTS ../workflow/_lib) find_library(Srpc_LIB srpc HINTS ../_lib) set(SRPC_LIB ${Srpc_LIB} ${Workflow_LIB} pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ) elseif (WIN32) set(SRPC_LIB srpc workflow ws2_32 wsock32 OpenSSL::SSL OpenSSL::Crypto protobuf::libprotobuf ZLIB::ZLIB Snappy::snappy ${LZ4_LIBRARY} ) else () set(SRPC_LIB srpc workflow pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ${SNAPPY_LIB} ${LZ4_LIB} ) endif () add_executable(server server.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(server ${SRPC_LIB}) add_dependencies(server BENCHMARK_GEN) add_executable(client client.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(client ${SRPC_LIB}) add_dependencies(client BENCHMARK_GEN) add_executable(client_cdf client_cdf.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(client_cdf ${SRPC_LIB}) add_dependencies(client_cdf BENCHMARK_GEN) add_executable(proxy proxy.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(proxy ${SRPC_LIB}) add_dependencies(proxy BENCHMARK_GEN) sogou-srpc-ac65014/benchmark/GNUmakefile000066400000000000000000000017731525217424400201620ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) ALL_TARGETS := all clean MAKE_FILE := Makefile DEFAULT_BUILD_DIR := build.cmake BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi) CMAKE3 := $(shell if which cmake3>/dev/null ; then echo cmake3; else echo cmake; fi;) .PHONY: $(ALL_TARGETS) all: mkdir -p $(BUILD_DIR) ifeq ($(DEBUG),y) cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR) else ifneq ("${Workflow_DIR}workflow", "workflow") cd $(BUILD_DIR) && $(CMAKE3) -DWorkflow_DIR:STRING=${Workflow_DIR} $(ROOT_DIR) else cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) endif make -C $(BUILD_DIR) -f Makefile clean: ifeq ($(MAKE_FILE), $(wildcard $(MAKE_FILE))) -make -f Makefile clean else ifeq ($(DEFAULT_BUILD_DIR), $(wildcard $(DEFAULT_BUILD_DIR))) -make -C $(DEFAULT_BUILD_DIR) clean endif rm -rf $(DEFAULT_BUILD_DIR) #g++ -o thrift_server thrift_server.cc gen-cpp/*.cpp -O2 -g -lthrift -lthriftnb -levent -lpthread -std=c++11 sogou-srpc-ac65014/benchmark/benchmark_pb.proto000066400000000000000000000003241525217424400215770ustar00rootroot00000000000000syntax="proto3"; message EmptyPBMsg { } message FixLengthPBMsg { bytes msg = 1; } service BenchmarkPB { rpc echo_pb(FixLengthPBMsg) returns (EmptyPBMsg); rpc slow_pb(FixLengthPBMsg) returns (EmptyPBMsg); } sogou-srpc-ac65014/benchmark/benchmark_thrift.thrift000066400000000000000000000001371525217424400226350ustar00rootroot00000000000000service BenchmarkThrift { void echo_thrift(1:string msg); void slow_thrift(1:string msg); } sogou-srpc-ac65014/benchmark/client.cc000066400000000000000000000120421525217424400176640ustar00rootroot00000000000000#include #include #include #include #include #include #include "benchmark_pb.srpc.h" #include "benchmark_thrift.srpc.h" using namespace srpc; #define TEST_SECOND 20 #define GET_CURRENT_NS std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() std::atomic query_count(0); std::atomic success_count(0); std::atomic error_count(0); std::atomic latency_sum(0); volatile bool stop_flag = false; int PARALLEL_NUMBER; std::string request_msg; template static void do_echo_pb(CLIENT *client) { FixLengthPBMsg req; req.set_msg(request_msg); int64_t ns_st = GET_CURRENT_NS; ++query_count; client->echo_pb(&req, [client, ns_st](EmptyPBMsg *response, RPCContext *ctx) { if (ctx->success()) { //printf("%s\n", ctx->get_remote_ip().c_str()); latency_sum += GET_CURRENT_NS - ns_st; ++success_count; } else { printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); ++error_count; } if (!stop_flag) do_echo_pb(client); //printf("echo done. seq_id=%d\n", ctx->get_task_seq()); }); } template static void do_echo_thrift(CLIENT *client) { BenchmarkThrift::echo_thriftRequest req; req.msg = request_msg; int64_t ns_st = GET_CURRENT_NS; ++query_count; client->echo_thrift(&req, [client, ns_st](BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) { if (ctx->success()) { //printf("%s\n", ctx->get_remote_ip().c_str()); latency_sum += GET_CURRENT_NS - ns_st; ++success_count; } else { printf("status[%d] error[%d] errmsg:%s \n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); ++error_count; } if (!stop_flag) do_echo_thrift(client); //printf("echo done. seq_id=%d\n", ctx->get_task_seq()); }); } int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 7) { fprintf(stderr, "Usage: %s \n", argv[0]); abort(); } WFGlobalSettings setting = GLOBAL_SETTINGS_DEFAULT; setting.endpoint_params.max_connections = 2048; setting.poller_threads = 16; setting.handler_threads = 16; WORKFLOW_library_init(&setting); RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.task_params.keep_alive_timeout = -1; client_params.host = argv[1]; client_params.port = atoi(argv[2]); std::string server_type = argv[3]; std::string idl_type = argv[4]; PARALLEL_NUMBER = atoi(argv[5]); int REQUEST_BYTES = atoi(argv[6]); request_msg.resize(REQUEST_BYTES, 'r'); //for (int i = 0; i < REQUEST_BYTES; i++) // request_msg[i] = (unsigned char)(rand() % 256); int64_t start = GET_CURRENT_MS(); if (server_type == "srpc") { if (idl_type == "pb") { auto *client = new BenchmarkPB::SRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) do_echo_pb(client); } else if (idl_type == "thrift") { auto *client = new BenchmarkThrift::SRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) do_echo_thrift(client); } else abort(); } else if (server_type == "brpc") { auto *client = new BenchmarkPB::BRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") do_echo_pb(client); else if (idl_type == "thrift") abort(); else abort(); } } else if (server_type == "thrift") { auto *client = new BenchmarkThrift::ThriftClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") abort(); else if (idl_type == "thrift") do_echo_thrift(client); else abort(); } } else if (server_type == "srpc_http") { if (idl_type == "pb") { auto *client = new BenchmarkPB::SRPCHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) do_echo_pb(client); } else if (idl_type == "thrift") { auto *client = new BenchmarkThrift::SRPCHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) do_echo_thrift(client); } else abort(); } else if (server_type == "thrift_http") { auto *client = new BenchmarkThrift::ThriftHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") abort(); else if (idl_type == "thrift") do_echo_thrift(client); else abort(); } } else abort(); std::this_thread::sleep_for(std::chrono::seconds(TEST_SECOND)); stop_flag = true; int64_t end = GET_CURRENT_MS(); int tot = query_count; int s = success_count; int e = error_count; int64_t l = latency_sum; fprintf(stderr, "\nquery\t%d\ttimes, %d success, %d error.\n", tot, s, e); fprintf(stderr, "total\t%.3lf\tseconds\n", (end - start) / 1000.0); fprintf(stderr, "qps=%.0lf\n", tot * 1000.0 / (end - start)); fprintf(stderr, "latency=%.0lfus\n", s > 0 ? l * 1.0 / s / 1000 : 0); std::this_thread::sleep_for(std::chrono::seconds(1)); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/benchmark/client_cdf.cc000066400000000000000000000171031525217424400205030ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "benchmark_pb.srpc.h" #include "benchmark_thrift.srpc.h" using namespace srpc; #define TEST_SECOND 20 #define GET_CURRENT_NS std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()).count() std::atomic query_count(0); std::atomic slow_count(0); std::atomic success_count(0); std::atomic error_count(0); //std::atomic latency_sum(0); std::vector> latency_lists; volatile bool stop_flag = false; int PARALLEL_NUMBER; std::string request_msg; int QPS; template static void do_echo_pb(CLIENT *client, int idx) { std::mutex mutex; auto& latency_list = latency_lists[idx]; FixLengthPBMsg req; req.set_msg(request_msg); int usleep_gap = 1000000 / QPS * PARALLEL_NUMBER; while (!stop_flag) { int64_t ns_st = GET_CURRENT_NS; if (++query_count % 100 > 0) { client->echo_pb(&req, [ns_st, &latency_list, &mutex](EmptyPBMsg *response, RPCContext *ctx) { if (ctx->success()) { //printf("%s\n", ctx->get_remote_ip().c_str()); ++success_count; //latency_sum += GET_CURRENT_NS - ns_st; mutex.lock(); latency_list.emplace_back(GET_CURRENT_NS - ns_st); mutex.unlock(); } else { printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); ++error_count; } //printf("echo done. seq_id=%d\n", ctx->get_task_seq()); }); } else { client->slow_pb(&req, [](EmptyPBMsg *response, RPCContext *ctx) { slow_count++; if (!ctx->success()) printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); } std::this_thread::sleep_for(std::chrono::microseconds(usleep_gap)); } } template static void do_echo_thrift(CLIENT *client, int idx) { std::mutex mutex; auto& latency_list = latency_lists[idx]; BenchmarkThrift::echo_thriftRequest req; req.msg = request_msg; BenchmarkThrift::slow_thriftRequest slow_req; slow_req.msg = request_msg; int usleep_gap = 1000000 / QPS * PARALLEL_NUMBER; while (!stop_flag) { int64_t ns_st = GET_CURRENT_NS; if (++query_count % 100 > 0) { client->echo_thrift(&req, [ns_st, &latency_list, &mutex](BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) { if (ctx->success()) { //printf("%s\n", ctx->get_remote_ip().c_str()); ++success_count; //latency_sum += GET_CURRENT_NS - ns_st; mutex.lock(); latency_list.emplace_back(GET_CURRENT_NS - ns_st); mutex.unlock(); } else { printf("status[%d] error[%d] errmsg:%s \n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); ++error_count; } //printf("echo done. seq_id=%d\n", ctx->get_task_seq()); }); } else { client->slow_thrift(&slow_req, [](BenchmarkThrift::slow_thriftResponse *response, RPCContext *ctx) { slow_count++; if (!ctx->success()) printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); } std::this_thread::sleep_for(std::chrono::microseconds(usleep_gap)); } } int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 8) { fprintf(stderr, "Usage: %s \n", argv[0]); abort(); } WFGlobalSettings setting = GLOBAL_SETTINGS_DEFAULT; setting.endpoint_params.max_connections = 2048; setting.poller_threads = 16; setting.handler_threads = 16; WORKFLOW_library_init(&setting); RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.task_params.keep_alive_timeout = -1; client_params.host = argv[1]; client_params.port = atoi(argv[2]); std::string server_type = argv[3]; std::string idl_type = argv[4]; PARALLEL_NUMBER = atoi(argv[5]); int REQUEST_BYTES = atoi(argv[6]); QPS = atoi(argv[7]); request_msg.resize(REQUEST_BYTES, 'r'); //for (int i = 0; i < REQUEST_BYTES; i++) // request_msg[i] = (unsigned char)(rand() % 256); latency_lists.resize(PARALLEL_NUMBER); std::vector th; int64_t start = GET_CURRENT_MS(); if (server_type == "srpc") { if (idl_type == "pb") { auto *client = new BenchmarkPB::SRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) th.push_back(new std::thread(do_echo_pb, client, i)); } else if (idl_type == "thrift") { auto *client = new BenchmarkThrift::SRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) th.push_back(new std::thread(do_echo_thrift, client, i)); } else abort(); } else if (server_type == "brpc") { auto *client = new BenchmarkPB::BRPCClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") th.push_back(new std::thread(do_echo_pb, client, i)); else if (idl_type == "thrift") abort(); else abort(); } } else if (server_type == "thrift") { auto *client = new BenchmarkThrift::ThriftClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") abort(); else if (idl_type == "thrift") th.push_back(new std::thread(do_echo_thrift, client, i)); else abort(); } } else if (server_type == "srpc_http") { if (idl_type == "pb") { auto * client = new BenchmarkPB::SRPCHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) th.push_back(new std::thread(do_echo_pb, client, i)); } else if (idl_type == "thrift") { auto *client = new BenchmarkThrift::SRPCHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) th.push_back(new std::thread(do_echo_thrift, client, i)); } else abort(); } else if (server_type == "thrift_http") { auto *client = new BenchmarkThrift::ThriftHttpClient(&client_params); for (int i = 0; i < PARALLEL_NUMBER; i++) { if (idl_type == "pb") abort(); else if (idl_type == "thrift") th.push_back(new std::thread(do_echo_thrift, client, i)); else abort(); } } else abort(); std::this_thread::sleep_for(std::chrono::seconds(TEST_SECOND)); stop_flag = true; for (auto *t : th) { t->join(); delete t; } int64_t end = GET_CURRENT_MS(); int tot = query_count - slow_count; int s = success_count; int e = error_count; int64_t l = 0;//latency_sum; std::vector all_lc; for (const auto& list : latency_lists) { for (auto v : list) { //fprintf(stderr, "%lld\n", (long long int)v); l += v; } all_lc.insert(all_lc.end(), list.begin(), list.end()); } sort(all_lc.begin(), all_lc.end()); for (double r = 0.950; r <= 0.999; r += 0.001) { double d = r * all_lc.size(); int idx = (int)(d + 1.0e-8); if (fabs(d - int(d)) > 1.0e-8) idx++; printf("%.3lf %lld\n", r, (long long int)all_lc[idx - 1]/1000); } //printf("%.3lf %lld\n", 1.0, (long long int)all_lc[all_lc.size() - 1]/1000); fprintf(stderr, "\nquery\t%d\ttimes, %d success, %d error.\n", tot, s, e); fprintf(stderr, "total\t%.3lf\tseconds\n", (end - start) / 1000.0); fprintf(stderr, "qps=%.0lf\n", tot * 1000.0 / (end - start)); fprintf(stderr, "latency=%.0lfus\n", s > 0 ? l * 1.0 / s / 1000 : 0); std::this_thread::sleep_for(std::chrono::seconds(1)); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/benchmark/proxy.cc000066400000000000000000000124771525217424400176030ustar00rootroot00000000000000#include #include #include "benchmark_pb.srpc.h" #include "benchmark_thrift.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; std::atomic query_count(0); // per_second std::atomic last_timestamp(0L); //volatile bool stop_flag = false; int max_qps = 0; long long total_count = 0; std::string remote_host; unsigned short remote_port; WFFacilities::WaitGroup wait_group(1); inline void collect_qps() { int64_t ms_timestamp = GET_CURRENT_MS(); ++query_count; if (ms_timestamp / 1000 > last_timestamp) { last_timestamp = ms_timestamp / 1000; int count = query_count; query_count = 0; total_count += count; if (count > max_qps) max_qps = count; long long ts = ms_timestamp; fprintf(stdout, "TIMESTAMP(ms) = %llu QPS = %d\n", ts, count); } } template class BenchmarkPBServiceImpl : public BenchmarkPB::Service { public: void echo_pb(FixLengthPBMsg *request, EmptyPBMsg *response, RPCContext *ctx) override { auto *task = this->client->create_echo_pb_task( [](EmptyPBMsg *remote_resp, srpc::RPCContext *remote_ctx) { collect_qps(); }); task->user_data = response; task->serialize_input(request); ctx->get_series()->push_back(task); } void slow_pb(FixLengthPBMsg *request, EmptyPBMsg *response, RPCContext *ctx) override { auto *task = WFTaskFactory::create_timer_task(15000, nullptr); ctx->get_series()->push_back(task); } CLIENT *client; }; template class BenchmarkThriftServiceImpl : public BenchmarkThrift::Service { public: void echo_thrift(BenchmarkThrift::echo_thriftRequest *request, BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) override { auto *task = this->client->create_echo_thrift_task( [](BenchmarkThrift::echo_thriftResponse *remote_resp, srpc::RPCContext *remote_ctx) { collect_qps(); }); task->user_data = response; task->serialize_input(request); ctx->get_series()->push_back(task); } void slow_thrift(BenchmarkThrift::slow_thriftRequest *request, BenchmarkThrift::slow_thriftResponse *response, RPCContext *ctx) override { auto *task = WFTaskFactory::create_timer_task(15000, nullptr); ctx->get_series()->push_back(task); } CLIENT *client; }; static void sig_handler(int signo) { wait_group.done(); } template class SERVICE, class CLIENT> static void init_proxy_client(SERVICE& service_impl) { RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.task_params.keep_alive_timeout = -1; client_params.host = remote_host; client_params.port = remote_port; service_impl.client = new CLIENT(&client_params); } static void run_srpc_proxy(unsigned short port) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SRPCServer proxy_server(¶ms); BenchmarkPBServiceImpl pb_impl; BenchmarkThriftServiceImpl thrift_impl; init_proxy_client(pb_impl); init_proxy_client(thrift_impl); proxy_server.add_service(&pb_impl); proxy_server.add_service(&thrift_impl); if (proxy_server.start(port) == 0) { wait_group.wait(); proxy_server.stop(); } else perror("server start"); } template static void run_pb_proxy(unsigned short port) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SERVER server(¶ms); BenchmarkPBServiceImpl pb_impl; init_proxy_client(pb_impl); server.add_service(&pb_impl); if (server.start(port) == 0) { wait_group.wait(); server.stop(); } else perror("server start"); } template static void run_thrift_proxy(unsigned short port) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SERVER server(¶ms); BenchmarkThriftServiceImpl thrift_impl; init_proxy_client(thrift_impl); server.add_service(&thrift_impl); if (server.start(port) == 0) { wait_group.wait(); server.stop(); } else perror("server start"); } int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; if (argc != 5) { fprintf(stderr, "Usage: %s " " \n", argv[0]); abort(); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); WFGlobalSettings my = GLOBAL_SETTINGS_DEFAULT; my.poller_threads = 16; my.handler_threads = 16; WORKFLOW_library_init(&my); unsigned short port = atoi(argv[1]); remote_host = argv[2]; remote_port = atoi(argv[3]); std::string server_type = argv[4]; if (server_type == "srpc") run_srpc_proxy(port); else if (server_type == "brpc") run_pb_proxy(port); else if (server_type == "thrift") run_thrift_proxy(port); else if (server_type == "srpc_http") run_pb_proxy(port); else if (server_type == "thrift_http") run_thrift_proxy(port); else abort(); fprintf(stdout, "\nTotal query: %llu max QPS: %d\n", total_count, max_qps); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/benchmark/server.cc000066400000000000000000000122241525217424400177160ustar00rootroot00000000000000#include #include #include "benchmark_pb.srpc.h" #include "benchmark_thrift.srpc.h" #include "workflow/WFFacilities.h" #ifdef _WIN32 #include "workflow/PlatformSocket.h" #else #include #include #include #include #include #endif using namespace srpc; std::atomic query_count(0); // per_second std::atomic last_timestamp(0L); //volatile bool stop_flag = false; int max_qps = 0; long long total_count = 0; WFFacilities::WaitGroup wait_group(1); inline void collect_qps() { int64_t ms_timestamp = GET_CURRENT_MS(); ++query_count; if (ms_timestamp / 1000 > last_timestamp) { last_timestamp = ms_timestamp / 1000; int count = query_count; query_count = 0; total_count += count; if (count > max_qps) max_qps = count; long long ts = ms_timestamp; fprintf(stdout, "TIMESTAMP(ms) = %llu QPS = %d\n", ts, count); } } class BenchmarkPBServiceImpl : public BenchmarkPB::Service { public: void echo_pb(FixLengthPBMsg *request, EmptyPBMsg *response, RPCContext *ctx) override { collect_qps(); } void slow_pb(FixLengthPBMsg *request, EmptyPBMsg *response, RPCContext *ctx) override { auto *task = WFTaskFactory::create_timer_task(15000, nullptr); ctx->get_series()->push_back(task); } }; class BenchmarkThriftServiceImpl : public BenchmarkThrift::Service { public: void echo_thrift(BenchmarkThrift::echo_thriftRequest *request, BenchmarkThrift::echo_thriftResponse *response, RPCContext *ctx) override { collect_qps(); } void slow_thrift(BenchmarkThrift::slow_thriftRequest *request, BenchmarkThrift::slow_thriftResponse *response, RPCContext *ctx) override { auto *task = WFTaskFactory::create_timer_task(15000, nullptr); ctx->get_series()->push_back(task); } }; static void sig_handler(int signo) { wait_group.done(); } static inline int create_bind_socket(unsigned short port) { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd >= 0) { struct sockaddr_in sin = { }; sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&sin, sizeof sin) >= 0) return sockfd; close(sockfd); } return -1; } static void run_srpc_server(unsigned short port, int proc_num) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SRPCServer server(¶ms); BenchmarkPBServiceImpl pb_impl; BenchmarkThriftServiceImpl thrift_impl; server.add_service(&pb_impl); server.add_service(&thrift_impl); int sockfd = create_bind_socket(port); if (sockfd < 0) { perror("create socket"); exit(1); } while ((proc_num /= 2) != 0) fork(); if (server.serve(sockfd) == 0) { wait_group.wait(); server.stop(); } else perror("server start"); close(sockfd); } template static void run_pb_server(unsigned short port, int proc_num) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SERVER server(¶ms); BenchmarkPBServiceImpl pb_impl; server.add_service(&pb_impl); int sockfd = create_bind_socket(port); if (sockfd < 0) { perror("create socket"); exit(1); } while ((proc_num /= 2) != 0) fork(); if (server.serve(sockfd) == 0) { wait_group.wait(); server.stop(); } else perror("server start"); close(sockfd); } template static void run_thrift_server(unsigned short port, int proc_num) { RPCServerParams params = RPC_SERVER_PARAMS_DEFAULT; params.max_connections = 2048; SERVER server(¶ms); BenchmarkThriftServiceImpl thrift_impl; server.add_service(&thrift_impl); int sockfd = create_bind_socket(port); if (sockfd < 0) { perror("create socket"); exit(1); } while ((proc_num /= 2) != 0) fork(); if (server.serve(sockfd) == 0) { wait_group.wait(); server.stop(); } else perror("server start"); close(sockfd); } int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; int proc_num = 1; if (argc == 4) { proc_num = atoi(argv[3]); if (proc_num != 1 && proc_num != 2 && proc_num != 4 && proc_num != 8 && proc_num != 16) { fprintf(stderr, "Usage: %s [proc num (1/2/4/8/16)]\n", argv[0]); abort(); } } else if (argc != 3) { fprintf(stderr, "Usage: %s [proc num (1/2/4/8/16)]\n", argv[0]); abort(); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); WFGlobalSettings my = GLOBAL_SETTINGS_DEFAULT; my.poller_threads = 16; my.handler_threads = 16; WORKFLOW_library_init(&my); unsigned short port = atoi(argv[1]); std::string server_type = argv[2]; if (server_type == "srpc") run_srpc_server(port, proc_num); else if (server_type == "brpc") run_pb_server(port, proc_num); else if (server_type == "thrift") run_thrift_server(port, proc_num); else if (server_type == "srpc_http") run_pb_server(port, proc_num); else if (server_type == "thrift_http") run_thrift_server(port, proc_num); else abort(); fprintf(stdout, "\nTotal query: %llu max QPS: %d\n", total_count, max_qps); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/benchmark/test.py000066400000000000000000000011751525217424400174350ustar00rootroot00000000000000import os import time #serverlist = [("srpc", "pb"), ("brpc", "pb"), ("thrift", "thrift")] serverlist = [("thrift", "thrift")] #reqlist = [16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768] parlist = [1, 2, 4, 8, 16, 32, 64, 128, 256] for server, idl in serverlist: #os.system("nohup ./server 8811 %s &" % server) for par in parlist: #for reqsize in reqlist: #cmd = "./echo_client %s" % reqsize #cmd = "./client 127.0.0.1 8811 %s %s 100 %s" % (server, idl, reqsize) cmd = "./client 127.0.0.1 8811 %s %s %s 1024" % (server, idl, par) print cmd os.system(cmd); time.sleep(1); #os.system("killall server") sogou-srpc-ac65014/benchmark/thrift_server.cc000066400000000000000000000027051525217424400213010ustar00rootroot00000000000000#include "gen-cpp/BenchmarkThrift.h" #include #include #include #include #include #include using namespace ::apache::thrift; using namespace ::apache::thrift::protocol; using namespace ::apache::thrift::transport; using namespace ::apache::thrift::server; using namespace ::apache::thrift::concurrency; using boost::shared_ptr; class BenchmarkThriftHandler : virtual public BenchmarkThriftIf { public: void echo_thrift(const std::string& msg) { } void slow_thrift(const std::string& msg) { usleep(15000); } }; int main(int argc, char **argv) { int port = 8811; shared_ptr handler(new BenchmarkThriftHandler()); shared_ptr processor(new BenchmarkThriftProcessor(handler)); shared_ptr protocolFactory(new TBinaryProtocolFactory()); boost::shared_ptr threadManager = ThreadManager::newSimpleThreadManager(16); boost::shared_ptr threadFactory = boost::shared_ptr(new PosixThreadFactory()); TNonblockingServer server(processor, protocolFactory, port, threadManager); server.setMaxConnections(2048); server.setNumIOThreads(16); threadManager->threadFactory(threadFactory); threadManager->start(); server.serve(); return 0; } sogou-srpc-ac65014/docs/000077500000000000000000000000001525217424400150765ustar00rootroot00000000000000sogou-srpc-ac65014/docs/docs-01-idl.md000066400000000000000000000013101525217424400173270ustar00rootroot00000000000000[English version](/docs/en/docs-01-idl.md) ## 01 - RPC IDL - æ��述文件 - å‰�å�Žå…¼å®¹ - Protobuf/Thrift ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 我们拿pb举例,定义一个ServiceName为``Example``çš„``example.proto``文件 - rpc接å�£å��为``Echo``,输入å�‚数为``EchoRequest``,输出å�‚数为``EchoResponse`` - ``EchoRequest``包括两个string:``message``å’Œ``name`` - ``EchoResponse``包括一个string:``message`` ~~~proto syntax="proto2"; message EchoRequest { optional string message = 1; optional string name = 2; }; message EchoResponse { optional string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ sogou-srpc-ac65014/docs/docs-02-service.md000066400000000000000000000022271525217424400202300ustar00rootroot00000000000000[English version](/docs/en/docs-02-service.md) ## 02 - RPC Service - 组æˆ�SRPCæœ�务的基本å�•å…ƒ - æ¯�一个Service一定由æŸ�一ç§�IDL生æˆ� - Serviceå�ªä¸ŽIDL有关,与网络通信具体å��议无关 ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„``example.proto``IDLæ��述文件 - 执行官方的``protoc example.proto --cpp_out=./ --proto_path=./``获得``example.pb.h``å’Œ``example.pb.cpp``两个文件 - 执行SRPCçš„``srpc_generator protobuf ./example.proto ./``获得``example.srpc.h``文件 - 我们派生``Example::Service``æ�¥å®žçŽ°å…·ä½“çš„rpc业务逻辑,这就是一个RPC Service - 注æ„�这个Service没有任何网络ã€�端å�£ã€�通信å��议等概念,仅仅负责完æˆ�实现从``EchoRequest``输入到输出``EchoResponse``的业务逻辑 ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; ~~~ sogou-srpc-ac65014/docs/docs-03-server.md000066400000000000000000000047421525217424400201030ustar00rootroot00000000000000[English version](/docs/en/docs-03-server.md) ## 03 - RPC Server - æ¯�一个Server对应一个端å�£ - æ¯�一个Server对应一个确定的网络通信å��è®® - æ¯�一个Serviceå�¯ä»¥æ·»åŠ åˆ°ä»»æ„�çš„Server里 - æ¯�一个Serverå�¯ä»¥æ‹¥æœ‰ä»»æ„�çš„Service,但在当å‰�Server里ServiceName必须唯一 - ä¸�å�ŒIDLçš„Service是å�¯ä»¥æ”¾è¿›å�Œä¸€ä¸ªServer中的 ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„``ExampleServiceImpl``Service - 首先,我们创建1个RPC Serverã€�需è¦�确定å��è®® - ç„¶å�Žï¼Œæˆ‘们å�¯ä»¥åˆ›å»ºä»»æ„�个数的Service实例ã€�ä»»æ„�ä¸�å�Œprotoå½¢æˆ�çš„Service,把这些Service通过``add_service()``接å�£æ·»åŠ åˆ°Server里 - 最å�Žï¼Œé€šè¿‡Serverçš„``start``或者``serve``å¼€å�¯æœ�务,处ç�†å�³å°†åˆ°æ�¥çš„rpc请求 - 想åƒ�一下,我们也å�¯ä»¥ä»Ž``Example::Service``派生更多的功能的rpc``Echo``ä¸�å�Œå®žçŽ°çš„Service - 想åƒ�一下,我们å�¯ä»¥åœ¨N个ä¸�å�Œçš„端å�£åˆ›å»ºN个ä¸�å�Œçš„RPC Serverã€�代表ç�€ä¸�å�Œçš„å��è®® - 想åƒ�一下,我们å�¯ä»¥æŠŠå�Œä¸€ä¸ªServiceIMPL实例``add_service``到ä¸�å�Œçš„Server上,我们也å�¯ä»¥æŠŠä¸�å�Œçš„ServiceIMPL实例``add_service``到å�Œä¸€ä¸ªServer上 - 想åƒ�一下,我们å�¯ä»¥ç”¨å�Œä¸€ä¸ª``ExampleServiceImpl``,在三个ä¸�å�Œç«¯å�£ã€�å�Œæ—¶æœ�务于BRPC-STDã€�SRPC-STDã€�SRPC-Http - 甚至,我们å�¯ä»¥å°†1个PBçš„``ExampleServiceImpl``å’Œ1个Thriftçš„``AnotherThriftServiceImpl``,``add_service``到å�Œä¸€ä¸ªSRPC-STD Server,两ç§�IDL在å�Œä¸€ä¸ªç«¯å�£ä¸Šå®Œç¾Žå·¥ä½œï¼� ~~~cpp int main() { SRPCServer server_srpc; SRPCHttpServer server_srpc_http; BRPCServer server_brpc; ThriftServer server_thrift; TRPCServer server_trpc; TRPCHttpServer server_trpc_http; ExampleServiceImpl impl_pb; AnotherThriftServiceImpl impl_thrift; server_srpc.add_service(&impl_pb); server_srpc.add_service(&impl_thrift); server_srpc_http.add_service(&impl_pb); server_srpc_http.add_service(&impl_thrift); server_brpc.add_service(&impl_pb); server_thrift.add_service(&impl_thrift); server_trpc.add_service(&impl_pb); server_trpc_http.add_service(&impl_thrift); server_srpc.start(1412); server_srpc_http.start(8811); server_brpc.start(2020); server_thrift.start(9090); server_trpc.start(2022); server_trpc_http.start(8822); getchar(); server_trpc_http.stop(); server_trpc.stop(); server_thrift.stop(); server_brpc.stop(); server_srpc_http.stop(); server_srpc.stop(); return 0; } ~~~ sogou-srpc-ac65014/docs/docs-04-client.md000066400000000000000000000040531525217424400200470ustar00rootroot00000000000000[English version](/docs/en/docs-04-client.md) ## 04 - RPC Client - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„目标/一个确定的集群 - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„网络通信å��è®® - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„IDL ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„例å­�,client相对简å�•,直接调用å�³å�¯ - 通过``Example::XXXClient``创建æŸ�ç§�RPCçš„client实例,需è¦�目标的ip+port或url - 利用client实例直接调用rpc函数``Echo``å�³å�¯ï¼Œè¿™æ˜¯ä¸€æ¬¡å¼‚步请求,请求完æˆ�å�Žä¼šè¿›å…¥å›žè°ƒå‡½æ•° - 具体的RPC Context用法请看下一个段è�½ ~~~cpp #include #include "example.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello!"); req.set_name("SRPCClient"); WFFacilities::WaitGroup wait_group(1); client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } ~~~ ### å�¯åЍå�‚æ•° Clientå�¯ä»¥ç›´æŽ¥é€šè¿‡ä¼ å…¥ipã€�portå�¯åŠ¨ï¼Œæˆ–è€…é€šè¿‡å�‚æ•°å�¯åŠ¨ã€‚ 上é�¢çš„例å­�: ~~~cpp Example::SRPCClient client("127.0.0.1", 1412); ~~~ ç­‰å�ŒäºŽï¼š ~~~cpp struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT; param.host = "127.0.0.1"; param.port = 1412; Example::SRPCClient client(¶m); ~~~ 也等å�ŒäºŽï¼š ~~~cpp struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT; param.url = "srpc://127.0.0.1:1412"; Example::SRPCClient client(¶m); ~~~ 注æ„�这里一定è¦�使用`RPC_CLIENT_PARAMS_DEFAULT`去åˆ�始化我们的å�‚数,里边包å�«äº†ä¸€ä¸ª`RPCTaskParams`,包括默认的data_typeã€�compress_typeã€�é‡�试次数和多ç§�超时,具体结构å�¯ä»¥å�‚考[rpc_options.h](/src/rpc_options.h)。 sogou-srpc-ac65014/docs/docs-05-context.md000066400000000000000000000053741525217424400202650ustar00rootroot00000000000000[English version](/docs/en/docs-05-context.md) ## 05 - RPC Context - RPCContext专门用æ�¥è¾…助异步接å�£ï¼ŒServiceå’ŒClient通用 - æ¯�一个异步接å�£éƒ½ä¼šæ��ä¾›Context,用æ�¥ç»™ç”¨æˆ·æ��供更高级的功能,比如获å�–对方ipã€�获å�–连接seqidç­‰ - Context上一些功能是Server或Client独有的,比如Serverå�¯ä»¥è®¾ç½®å›žå¤�æ•°æ�®çš„压缩方å¼�,Clientå�¯ä»¥èŽ·å�–请求æˆ�功或失败 - Context上å�¯ä»¥é€šè¿‡get_series获得所在的series,与workflow的异步模å¼�æ— ç¼�结å�ˆ ### RPCContext API - Common #### ``long long get_seqid() const;`` 请求+回å¤�视为1次完整通信,获得当å‰�socket连接上的通信sequence id,seqid=0代表第1次 #### ``std::string get_remote_ip() const;`` 获得对方IP地å�€ï¼Œæ”¯æŒ�ipv4/ipv6 #### ``int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;`` 获得对方地å�€ï¼Œin/outå�‚数为更底层的数æ�®ç»“æž„sockaddr #### ``const std::string& get_service_name() const;`` 获å�–RPC Service Name #### ``const std::string& get_method_name() const;`` 获å�–RPC Methode Name #### ``SeriesWork *get_series() const;`` 获å�–当å‰�ServerTask/ClientTask所在series ### RPCContext API - Only for client done #### ``bool success() const;`` client专用。这次请求是å�¦æˆ�功 #### ``int get_status_code() const;`` client专用。这次请求的rpc status code #### ``const char *get_errmsg() const;`` client专用。这次请求的错误信æ�¯ #### ``int get_error() const;`` client专用。这次请求的错误ç � #### ``void *get_user_data() const;`` client专用。获å�–ClientTaskçš„user_data。如果用户通过create_xxx_task接å�£äº§ç”Ÿtask,则å�¯ä»¥é€šè¿‡user_data域记录上下文,在创建task时设置,在回调函数中拿回。 ### RPCContext API - Only for server process #### ``void set_data_type(RPCDataType type);`` Server专用。设置数æ�®æ‰“包类型 - RPCDataProtobuf - RPCDataThrift - RPCDataJson #### ``void set_compress_type(RPCCompressType type);`` Server专用。设置数æ�®åŽ‹ç¼©ç±»åž‹(注:Client的压缩类型在Client或Task上设置) - RPCCompressNone - RPCCompressSnappy - RPCCompressGzip - RPCCompressZlib - RPCCompressLz4 #### ``void set_attachment_nocopy(const char *attachment, size_t len);`` Server专用。设置attachment附件。 #### ``bool get_attachment(const char **attachment, size_t *len) const;`` Server专用。获å�–attachment附件。 #### ``void set_reply_callback(std::function cb);`` Server专用。设置reply callback,æ“�作系统写入socket缓冲区æˆ�功å�Žè¢«è°ƒç”¨ã€‚ #### ``void set_send_timeout(int timeout);`` Server专用。设置å�‘é€�超时,å�•ä½�毫秒。-1代表无é™�。 #### ``void set_keep_alive(int timeout);`` Server专用。设置连接ä¿�活时间,å�•ä½�毫秒。-1代表无é™�。 sogou-srpc-ac65014/docs/docs-06-workflow.md000066400000000000000000000126171525217424400204520ustar00rootroot00000000000000[English version](/docs/en/docs-06-workflow.md) ## 06 - 与workflow异步框架的结å�ˆ ### 1. Server 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - Echo RPC在接收到请求时,å�‘下游å�‘起一次http请求 - 对下游请求完æˆ�å�Žï¼Œæˆ‘们将http responseçš„bodyä¿¡æ�¯å¡«å……到responseçš„message里,回å¤�给客户端 - 我们ä¸�希望阻塞/å� æ�®ç�€Handler的线程,所以对下游的请求一定是一次异步请求 - 首先,我们通过Workflow框架的工厂``WFTaskFactory::create_http_task``创建一个异步任务http_task - ç„¶å�Žï¼Œæˆ‘们利用RPCContextçš„``ctx->get_series()``获å�–到ServerTask所在的SeriesWork - 最å�Žï¼Œæˆ‘们使用SeriesWorkçš„``push_back``接å�£å°†http_task放到SeriesWorkçš„å�Žé�¢ ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [request, response](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); response->mutable_message()->assign((const char *)data, len); } else response->set_message("Error: " + std::to_string(task->get_error())); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); }); ctx->get_series()->push_back(http_task); } }; ~~~ ### 2. Client 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 我们并行å�‘出两个请求,1个是rpc请求,1个是http请求 - 两个请求都结æ�Ÿå�Žï¼Œæˆ‘们å†�å�‘起一次计算任务,计算两个数的平方和 - 首先,我们通过RPC Clientçš„``create_Echo_task``创建一个rpc异步请求的网络任务rpc_task - ç„¶å�Žï¼Œæˆ‘们通过Workflow框架的工厂``WFTaskFactory::create_http_task``å’Œ``WFTaskFactory::create_go_task``分别创建异步网络任务http_task,和异步计算任务calc_task - 最å�Žï¼Œæˆ‘们利用串并è�”æµ�程图,乘å�·ä»£è¡¨å¹¶è¡Œã€�大于å�·ä»£è¡¨ä¸²è¡Œï¼Œå°†3个异步任务组å�ˆèµ·æ�¥æ‰§è¡Œstart ~~~cpp void calc(int x, int y) { int z = x * x + y * y; printf("calc result: %d\n", z); } int main() { Example::SRPCClient client("127.0.0.1", 1412); auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { std::string body; const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); body.assign((const char *)data, len); printf("%s\n\n", body.c_str()); } else printf("Http request fail\n\n"); }); auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4); EchoRequest req; req.set_message("Hello!"); req.set_name("1412"); rpc_task->serialize_input(&req); WFFacilities::WaitGroup wait_group(1); SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) { wait_group.done(); }); series->push_back(rpc_task); series->push_back(calc_task); series->start(); wait_group.wait(); return 0; } ~~~ ### 3. Upstream SRPCå�¯ä»¥ç›´æŽ¥ä½¿ç”¨Workflow的任何组件,最常用的就是[Upstream](https://github.com/sogou/workflow/blob/master/docs/about-upstream.md),SRPC的任何一ç§�client都å�¯ä»¥ä½¿ç”¨Upstream。 我们通过å�‚æ•°æ�¥çœ‹çœ‹å¦‚何构造å�¯ä»¥ä½¿ç”¨Upstreamçš„client: ```cpp #include "workflow/UpstreamManager.h" int main() { // 1. 创建upstream并添加实例 UpstreamManager::upstream_create_weighted_random("echo_server", true); UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412"); UpstreamManager::upstream_add_server("echo_server", "192.168.10.10"); UpstreamManager::upstream_add_server("echo_server", "internal.host.com"); // 2. 构造å�‚数,填上upstreamçš„å��å­— RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.host = "echo_server"; client_params.port = 1412; // 这个portå�ªç”¨äºŽupstream URIè§£æž�,ä¸�å½±å“�具体实例的选å�– // 3. 用å�‚数创建client,其他用法与示例类似 Example::SRPCClient client(&client_params); ... ``` 如果使用了ConsistentHash或者Manualæ–¹å¼�创建upstream,则我们往往需è¦�对ä¸�å�Œçš„task进行区分ã€�以供选å�–算法使用。这时候å�¯ä»¥ä½¿ç”¨client task上的`int set_uri_fragment(const std::string& fragment);`接å�£ï¼Œè®¾ç½®è¯·æ±‚级相关的信æ�¯ã€‚ 这个域的是URI里的fragment,语义请å�‚考[RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5),任何需è¦�用到fragment的功能(如其他选å�–策略里附带的其他信æ�¯ï¼‰ï¼Œéƒ½å�¯ä»¥åˆ©ç”¨è¿™ä¸ªåŸŸã€‚ sogou-srpc-ac65014/docs/docs-07-srpc-http.md000066400000000000000000000153221525217424400205210ustar00rootroot00000000000000[English version](/docs/docs-07-srpc-http.md) ## 07 - 使用SRPCã€�TRPCã€�Thriftå�‘é€�Http **SRPC**支æŒ�**HTTP**å��议,å�ªè¦�把**idl**的内容作填到**HTTP**çš„**body**中,并且在**header**里填上**idl**的类型(**json**/**protobuf**/**thrift**),就å�¯ä»¥ä¸Žå…¶ä»–框架通过**HTTP**å��议互通,由此å�¯ä»¥å®žçŽ°è·¨è¯­è¨€ã€‚ - å�¯åЍ**SRPCHttpServer**/**TRPCHttpServer**/**ThriftHttpServer**,å�¯ä»¥æŽ¥æ”¶ç”±ä»»ä½•语言实现的HTTP clientå�‘过æ�¥çš„请求; - å�¯åЍ**SRPCHttpClient**/**TRPCHttpClient**/**ThriftHttpClient**,也å�¯ä»¥å�‘任何语言实现的Http Serverå�‘é€�请求; - **HTTP header**:`Content-Type`设置为`application/json`表示json,`application/x-protobuf`表示protobuf,`application/x-thrift`表示thriftï¼› - **HTTP body**: 如果body中涉å�Š**bytes**类型,**json**中需è¦�使用**base64**进行encodeï¼› ### 1. 示例 想实现**SRPCHttpClient**,å�¯ä»¥æŠŠ[tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc)或者[tutorial-09-client_task.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-09-client_task.cc)中的`SRPCClient`改æˆ�`SRPCHttpClient`å�³å�¯ã€‚ 在项目的[README.md](/docs//README_cn.md#6-run)中,我们演示了如何使用**curl**å�‘**SRPCHttpServer**å�‘é€�请求,下é�¢æˆ‘们给出例å­�演示如何使用**python**作为客户端,å�‘**TRPCHttpServer**å�‘é€�请求。 **proto文件:** ```proto syntax="proto3"; // proto2 or proto3 are both supported package trpc.test.helloworld; message AddRequest { string message = 1; string name = 2; bytes info = 3; int32 error = 4; }; message AddResponse { string message = 1; }; service Batch { rpc Add(AddRequest) returns (AddResponse); }; ``` **python客户端:** ```py import json import requests from base64 import b64encode class Base64Encoder(json.JSONEncoder): def default(self, o): if isinstance(o, bytes): return b64encode(o).decode() return json.JSONEncoder.default(self, o) headers = {'Content-Type': 'application/json'} req = { 'message': 'hello', 'name': 'k', 'info': b'i am binary' } print(json.dumps(req, cls=Base64Encoder)) ret = requests.post(url = "http://localhost:8800/trpc.test.helloworld.Batch/Add", headers = headers, data = json.dumps(req, cls=Base64Encoder)) print(ret.json()) ``` ### 2. 请求路径拼接 [README.md](/docs//README_cn.md#6-run)中,我们å�¯ä»¥çœ‹åˆ°ï¼Œè·¯å¾„是由serviceå��å’Œrpcå��拼接而æˆ�的。而对于以上带packageå�� `package trpc.test.helloworld;`的例å­�, packageå��也需è¦�拼接到路径中,**SRPCHttp** å’Œ **TRPCHttp** 的拼接路径方å¼�å¹¶ä¸�一样,而**ThriftHttp**由于SRPCçš„thriftä¸�支æŒ�多个service所以无需拼接任何路径。 我们以**curl**为例å­�: 与**SRPCHttpServer**互通: ```sh curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}' ``` 与**TRPCHttpServer**互通: ```sh curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}' ``` 与**ThriftHttpServer**互通: ```sh curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}' ``` ### 3. HTTP状æ€�ç � SRPC支æŒ�server在`process()`中设置状æ€�ç �,接å�£ä¸º**RPCContext**上的`set_http_code(int code)`。å�ªæœ‰åœ¨æ¡†æž¶èƒ½å¤Ÿæ­£ç¡®å¤„ç�†è¯·æ±‚的情况下,该错误ç �æ‰�有效,å�¦åˆ™ä¼šè¢«è®¾ç½®ä¸ºæ¡†æž¶å±‚级的错误ç �。 **用法:** ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { if (req->name() != "workflow") ctx->set_http_code(404); // 设置HTTP状æ€�ç �404 else resp->set_message("Hi back"); } }; ~~~ **CURL命令:** ~~~sh curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}' ~~~ **结果:** ~~~sh HTTP/1.1 404 Not Found SRPC-Status: 1 SRPC-Error: 0 Content-Type: application/json Content-Encoding: identity Content-Length: 21 Connection: Keep-Alive ~~~ **注æ„�:** 我们ä¾�ç„¶å�¯ä»¥é€šè¿‡è¿”回结果的header中的`SRPC-Status: 1`æ�¥åˆ¤æ–­è¿™ä¸ªè¯·æ±‚在框架层é�¢æ˜¯æ­£ç¡®çš„,`404`是æ�¥è‡ªserver的状æ€�ç �。 ### 4. HTTP Header 用户å�¯ä»¥é€šè¿‡ä»¥ä¸‹ä¸‰ä¸ªæŽ¥å�£æ�¥è®¾ç½®/获å�–http header: ~~~cpp bool get_http_header(const std::string& name, std::string& value) const; bool set_http_header(const std::string& name, const std::string& value); bool add_http_header(const std::string& name, const std::string& value); ~~~ 对于**server**æ�¥è¯´ï¼Œè¿™äº›æŽ¥å�£åœ¨`RPCContext`上。 对于**client**æ�¥è¯´ï¼Œéœ€è¦�通过`RPCClientTask`设置**请求上的http header**ã€�并且在回调函数的`RPCContext`上**获å�–回å¤�上的http header**,用法如下所示: ~~~cpp int main() { Example::SRPCHttpClient client("127.0.0.1", 80); EchoRequest req; req.set_message("Hello, srpc!"); auto *task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) { std::string value; ctx->get_http_header("server_key", value); // 获å�–回å¤�中的header } }); task->serialize_input(&req); task->set_http_header("client_key", "client_value"); // 设置请求中的header task->start(); wait_group.wait(); return 0; } ~~~ ### 5. IDL传输格å¼�问题 如果我们填写的是Protobuf且用的标准为proto3,æ¯�个域由于没有optionalå’Œrequired区分,所以都是带有默认值的。如果我们设置的值正好等于默认值,则proto3ä¸�能识别为被set过,就ä¸�能被åº�列化的时候å�‘出。 在protobuf转json的过程中,SRPC在**RPCContext上**æ��供了几个接å�£ï¼Œæ”¯æŒ� [JsonPrintOptions](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonPrintOptions) 上的功能。具体接å�£ä¸Žç”¨æ³•æ��è¿°å�¯ä»¥æŸ¥çœ‹ï¼š[rpc_context.h](/src/rpc_context.h) **示例:** ```cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); resp->set_error(0); // 0是error类型int32在proto3中的默认值 ctx->set_json_always_print_fields_with_no_presence(true); // 带上所有原始域 ctx->set_json_add_whitespace(true); // 增加jsonæ ¼å¼�的空格 } }; ``` **原始输出:** ```sh {"message":"Hi back"} ``` **通过RPCContext设置过json options之å�Žçš„输出:** ```sh { "message": "Hi back", "error": 0 } ``` sogou-srpc-ac65014/docs/docs-08-tracing.md000066400000000000000000000111431525217424400202220ustar00rootroot00000000000000[English version](/docs/en/docs-08-tracing.md) ## 08 - 上报Tracing到OpenTelemetry **SRPC**支æŒ�产生和上报链路信æ�¯traceå’Œspan,并且å�¯ä»¥é€šè¿‡å¤šç§�途径进行上报,其中包括本地导出数æ�®å’Œä¸ŠæŠ¥åˆ°[OpenTelemetry](https://opentelemetry.io). **SRPC**é�µå¾ª**OpenTelemetry**çš„[æ•°æ�®è§„范(data specification)](https://github.com/open-telemetry/opentelemetry-specification)以å�Š[w3cçš„trace context](https://www.w3.org/TR/trace-context/),因此å�¯ä»¥ä½¿ç”¨æ�’ä»¶**RPCTraceOpenTelemetry**进行上报。 秉承ç�€**Workflow**的风格,所有的上报都是异步任务模å¼�,对RPC的请求和æœ�务ä¸�会产生任何性能影å“�。 ### 1. 用法 先构造æ�’ä»¶`RPCTraceOpenTelemetry`,然å�Žé€šè¿‡`add_filter()`把æ�’件加到**server**或**client**中。 以[tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc)作为client的示例,我们如下加两行代ç �: ```cpp int main() { Example::SRPCClient client("127.0.0.1", 1412); RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318"); client.add_filter(&span_otel); ... } ``` 以[tutorial-01-srpc_pb_server.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-01-srpc_pb_server.cc)作为server的示例,也增加类似的两行。å�Œæ—¶æˆ‘们还增加一个本地æ�’件,用于把本次请求的traceæ•°æ�®ä¹Ÿåœ¨å±�幕上打å�°ï¼š ```cpp int main() { SRPCServer server; RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318"); server.add_filter(&span_otel); RPCTraceDefault span_log; // 这个æ�’件会把本次请求的traceä¿¡æ�¯æ‰“到å±�幕上 server.add_filter(&span_log); ... } ``` 执行命令`make tutorial`å�¯ä»¥ç¼–译出示例程åº�,并且分别把serverå’Œclientè·‘èµ·æ�¥ï¼Œæˆ‘们å�¯ä»¥åœ¨å±�幕上看到一些tracingä¿¡æ�¯ï¼š image 我们å�¯ä»¥çœ‹åˆ°ä¸Šå›¾client这边的span_id: **04d070f537f17d00**,它在下图server这里å�˜æˆ�了parent_span_id: **04d070f537f17d00**: image ### 2. Traces上报到Jaeger 打开Jaeger的主页,我们å�¯ä»¥æ‰¾åˆ°æˆ‘们å��为**Example**çš„æœ�务(service)å’Œå��为**Echo**的函数(method)。这一个tracing记录上有两个span节点,是由serverå’Œclient分别上报的。 image 我们å�¯ä»¥åœ¨Jaeger看到client所上报的span_id: **04d070f537f17d00**å’Œserver所上报的span_id: **00202cf737f17d00**,å�Œæ—¶ä¹Ÿæ˜¯èƒ½å¯¹åº”上刚æ‰�在å±�幕看到的id的。 image ### 3. å�‚æ•° 多久收集一份traceä¿¡æ�¯ã€�上报请求的é‡�试次数ã€�以å�Šå…¶ä»–å�‚数,都å�¯ä»¥é€šè¿‡`RPCTraceOpenTelemetry`的构造函数指定。代ç �å�‚考:[src/module/rpc_trace_filter.h](https://github.com/sogou/srpc/blob/master/src/module/rpc_trace_filter.h#L238) 默认æ¯�秒收集1000æ�¡traceä¿¡æ�¯ï¼Œå¹¶ä¸”é€�ä¼ tracingä¿¡æ�¯ç­‰å…¶ä»–功能也已é�µå¾ªä¸Šè¿°è§„范实现。 ### 4. Attributes 我们å�¯ä»¥é€šè¿‡`add_attributes()`添加æŸ�些é¢�外的信æ�¯ï¼Œæ¯”如数æ�®è§„范中的OTEL_RESOURCE_ATTRIBUTES。 注æ„�我们的serviceå��"Example"也是设置到attributes中的,key为`service.name`。如果用户也在OTEL_RESOURCE_ATTRIBUTES中使用了`service.name`这个key,则SRPCçš„service name优先级更高,å�‚考:[OpenTelemetry#resource](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry#resource) ### 5. Logå’ŒBaggage SRPCæ��供了`log()`å’Œ`baggage()`接å�£ï¼Œç”¨æˆ·å�¯ä»¥æ·»åŠ éœ€è¦�通过链路é€�传的数æ�®ã€‚ ```cpp void log(const RPCLogVector& fields); void baggage(const std::string& key, const std::string& value); ``` 作为Server,我们å�¯ä»¥ä½¿ç”¨`RPCContext`上的接å�£æ�¥æ·»åŠ log: ```cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); ctx->log({{"event", "info"}, {"message", "rpc server echo() end."}}); } }; ``` 作为client,我们å�¯ä»¥ä½¿ç”¨`RPCClientTask`上的接å�£æ·»åŠ log: ```cpp srpc::SRPCClientTask *task = client.create_Echo_task(...); task->log({{"event", "info"}, {"message", "log by rpc client echo()."}}); ``` sogou-srpc-ac65014/docs/docs-09-metrics.md000066400000000000000000000264111525217424400202460ustar00rootroot00000000000000[English version](/docs/en/docs-09-metrics.md) ## 09 - 上报Metrics **Metrics**(指标)是常用的监控需求,**SRPC**支æŒ�产生与统计Metrics,并通过多ç§�途径上报,其中包括上报到[Prometheus](https://prometheus.io/)å’Œ[OpenTelemetry](https://opentelemetry.io)。 秉承ç�€**Workflow**的风格,所有的上报都是异步任务推é€�或拉å�–模å¼�,对RPC的请求和æœ�务ä¸�会产生任何性能影å“�。 本文档会介ç»�Metrics的概念ã€�结å�ˆtutorial-16对接å�£è¿›è¡Œè®²è§£ã€�上报Prometheus的特点ã€�上报OpenTelemetry的特点ã€�以å�Šä»‹ç»�使用了thread local进行性能æ��速的Var模å�—。 ### 1. Metrics概念介ç»� **Prometheus**的数æ�®ç±»åž‹å�¯ä»¥å�‚考官方文档:[Concepts - Metrics](https://prometheus.io/docs/concepts/metric_types/) å’Œ [Type of Metrics](https://prometheus.io/docs/tutorials/understanding_metric_types/)。 **OpenTelemetry**çš„å�¯ä»¥å�‚考[æ•°æ�®è§„范(data specification)](https://github.com/open-telemetry/opentelemetry-specification)以å�Š[Metricsçš„datamodel.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md)。 其中四ç§�基本指标的概念都是一致的,因此SRPC对这四ç§�指标进行了最基本的支æŒ�,å�¯åˆ†åˆ«å¯¹åº”到Prometheuså’ŒOpenTelemetry的上报数æ�®ä¸­ã€‚å�‚考下表: |指标类型|Prometheus|OpenTelemetry| SRPC中的创建接å�£ | SRPC中的常用æ“�作 | |-------|----------|------------|-----------------|-------------------| |å�•个数值| Gauge | Gauge | create_gauge(name, help); | void increase(); void decrease();| |计数器 | Counter | Sum | create_counter(name, help); |GaugeVar *add(labels);| |直方图 |Histogram | Histogram | create_histogram(name, help, buckets); |void observe(data);| |采样 | Summary | Summary | create_summary(name, help, quantiles); |void observe(data);| å››ç§�指标的大致æ��述: 1. **å�•个数值**:简å�•数值的度é‡�,å�¯ä»¥ç®€å�•的增å‡�。 2. **计数器**:累计的度é‡�,å�¯ä»¥é€šè¿‡æ·»åŠ è‹¥å¹²**label**去区分ä¸�å�Œlabel下的数值。 3. **直方图**:对观察observer()得到的接å�£è¿›è¡Œåˆ†åŒºé—´ç´¯åŠ ï¼Œå› æ­¤éœ€è¦�ä¼ å…¥**buckets**告知需è¦�划分的区间; 比如传入的bucket分桶值是{ 1, 10, 100 }è¿™3个数,则我们å�¯ä»¥å¾—到数æ�®åˆ†å¸ƒäºŽ{ 0 - 1, 0 - 10, 0 - 100, 0 - +Inf }4个区间的数æ�®ç´¯è®¡å€¼ï¼Œå�Œæ—¶ä¹Ÿä¼šå¾—到整体的总以å�Šæ•°æ�®ä¸ªæ•°ã€‚ 4. **采样**:与直方图类似,但传入的是**分ä½�æ•°quantiles**,比如{0.5, 0.9}以å�Šå®ƒä»¬çš„精度,主è¦�用于事先ä¸�知é�“æ•°æ�®åˆ†å¸ƒå…·ä½“数值的场景(相比之下,直方图需è¦�传入具体数值)。 采样是带有**时间窗å�£**的,å�¯ä»¥é€šè¿‡æŽ¥å�£æŒ‡å®š**max_age统计窗å�£æ—¶é•¿**以å�Š**age_bucket内部分桶个数**,SRPC中默认的是60秒分5个桶切æ�¢æ—¶é—´çª—å�£ã€‚ 考虑到**采样Summary**本身的统计å¤�æ�‚性,一般æ�¥è¯´å»ºè®®ä¼˜å…ˆä½¿ç”¨**直方图Histogram**。 为了方便用户使用,目å‰�SRPC里这四ç§�指标都使用double类型进行统计。 ### 2. 用法示例 我们结å�ˆ[tutorial-16-server_with_metrics.cc](/tutorial/tutorial-16-server_with_metrics.cc)看看基本用法,本示例虽然是server,但client中的用法也是一样的。 #### (1) 创建æ�’ä»¶ 我们选择Prometheus作为我们的上报对象,因此需è¦�使用**RPCMetricsPull**æ�’件。 ~~~cpp #include "srpc/rpc_metrics_filter.h" // Metricsæ�’件所在的头文件 int main() { SRPCServer server; ExampleServiceImpl impl; RPCMetricsPull filter; // 创建一个æ�’ä»¶ filter.init(8080); // é…�å�ˆPrometheus中填好的收集数æ�®çš„端å�£ ~~~ #### (2) 添加指标 这个**RPCMetricsPull**æ�’件本身自带了统计部分常用指标,包括整体请求个数统计ã€�按照serviceå’Œmethod作为labelçš„ä¸�å�Œç»´åº¦çš„请求个数统计以å�Šè¯·æ±‚耗时的分ä½�数统计等。 用户å�¯ä»¥è‡ªè¡Œå¢žåŠ æƒ³è¦�统计的值,这里我们增加一个用于统计请求大å°�的直方图histogram,å��字为"echo_request_size",上报时的指标æ��è¿°ä¿¡æ�¯æ˜¯"Echo request size",数æ�®bucket划分是 { 1, 10, 100 } 。 ~~~cpp filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100}); ~~~ 说明: **添加指标的时机** 指标是å�¯ä»¥éš�时添加的,å�³ä½¿server/clientè·‘èµ·æ�¥ä¹‹å�Žä¹Ÿå�¯ä»¥ã€‚但**必须在æ“�作这个指标之å‰�添加**,å�¦åˆ™èŽ·å�–指标的时候会获å�–到空指针,无法进行统计æ“�作。 **指标的å��å­—** 指标的å��字是**全局唯一**的(无论是四ç§�基本类型中的哪ç§�),且**å�ªèƒ½åŒ…å�«å¤§å°�写字æ¯�和下划线,å�³a-z, A-Z, _** 。如果使用已ç»�存在的å��字创建指标,则会创建失败并返回NULL。 一旦创建æˆ�功,我们之å�Žéƒ½ä¼šä½¿ç”¨è¿™å�Œä¸€ä¸ªå��字去æ“�作这个指标。 **创建其他指标的接å�£** å�¯ä»¥æŸ¥çœ‹åˆšæ‰�include的头文件[rpc_metrics_filter.h](/src/module/rpc_metrics_filter.h)中的:`class RPCMetricsFilter`。 #### (3) 把æ�’件添加到server/client中 由于我们需è¦�æ“�作指标时,是需è¦�调用这个æ�’件上的接å�£çš„,因此我们在service中ä¿�留一下这个指针。这å�ªæ˜¯ç¤ºä¾‹ç¨‹åº�的用法,用户å�¯ä»¥ä½¿ç”¨è‡ªå·±ä¹ æƒ¯çš„æ–¹å¼�: ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) overrideï¼› void set_filter(RPCMetricsPull *filter) { this->filter = filter; } private: RPCMetricsPull *filter; // ä¿�留了æ�’件的指针,并实现接å�£è®¾ç½®è¿›åŽ»ï¼Œè¿™å¹¶é�žSRPC框架的接å�£ }; ~~~ main函数中继续这样写: ~~~cpp int main() { ... impl.set_filter(&filter); // 设置到我们刚æ‰�为service留的接å�£ä¸­ server.add_filter(&filter); // client也一样å�¯ä»¥è°ƒç”¨add_filter() server.add_service(&impl); filter.deinit(); return 0; } ~~~ #### (4) æ“�作指标 我们在æ¯�次收到请求的时候,都把EchoRequest的大å°�统计到刚æ‰�创建的直方图指标上: ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong()); } ~~~ å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们通过filter上的histogram()接å�£ï¼Œå°±å�¯ä»¥å¸¦ç�€åˆšæ‰�çš„å��字去到指标的指针,并且通过observe()填入数æ�®å¤§å°�。 å››ç§�基本类型的获å�–接å�£å¦‚下: ~~~cpp class RpcMetricsFilter : public RPCFilter { GaugeVar *gauge(const std::string& name); CounterVar *counter(const std::string& name); HistogramVar *histogram(const std::string& name); SummaryVar *summary(const std::string& name); ~~~ 如果找到,会返回四ç§�基本指标类型的指针,å�¯ä»¥å¦‚示例进行下一步æ“�作比如histogram的统计接å�£observe(),**注æ„�:ä¸�存在这个å��字则会返回空指针,因此è¦�ä¿�è¯�我们拿到的å�˜é‡�一定是æˆ�功创建过的**。 å››ç§�类型常用的æ“�作接å�£å¦‚上文表格所示,具体å�¯ä»¥å�‚考[rpc_var.h](/src/var/rpc_var.h)。 值得说明一下Counter类型的接å�£ï¼š ~~~cpp class CounterVar : public RPCVar { GaugeVar *add(const std::map& labels); ~~~ 接å�£å�¯ä»¥å¯¹Counter指标添加æŸ�个维度的Gauge值,å�„维度的统计是分开的,且labels为一个map,å�³å�¯ä»¥é€šè¿‡å¤šç»„label指定一个维度,比如: ~~~cpp filter->counter("service_method_count")->add({"service", "Example"}, {"method", "Echo"}})->increase(); ~~~ å°±å�¯ä»¥èŽ·å¾—ä¸€ä¸ªé’ˆå¯¹`{service="Example",method="Echo"}`统计出æ�¥çš„æ•°å€¼ã€‚ #### (5) 自动上报 SRPCçš„æ�’件都是自动上报的,因此无需用户调用任何接å�£ã€‚我们å°�试调用clientå�‘é€�请求产生一些统计数æ�®ï¼Œç„¶å�Žçœ‹çœ‹ä¸ŠæŠ¥å‡ºæ�¥çš„æ•°æ�®æ˜¯ä»€ä¹ˆã€‚ ~~~sh ./srpc_pb_client message: "Hi back" message: "Hi back" ~~~ 由于Prometheus是使用Pull模å¼�拉å�–,å�³ä¼šé€šè¿‡æˆ‘们注册到Prometheus的端å�£å’Œ/metrics进行拉å�–,也就是我们刚æ‰�åˆ�始化上报æ�’件需è¦�é…�对上端å�£çš„原因。通过与Prometheus相å�Œçš„æ–¹å¼�,我们å�¯ä»¥æœ¬åœ°è®¿é—®ä¸€ä¸‹ï¼Œä¼šçœ‹åˆ°è¿™æ ·çš„一些数æ�®ï¼š ~~~sh curl localhost:8080/metrics # HELP total_request_count total request count # TYPE total_request_count gauge total_request_count 2.000000 # HELP total_request_method request method statistics # TYPE total_request_method counter total_request_method{method="Echo",service="Example"} 2.000000 # HELP total_request_latency request latency nano seconds # TYPE total_request_latency summary total_request_latency{quantile="0.500000"} 645078.500000 total_request_latency{quantile="0.900000"} 645078.500000 total_request_latency_sum 1290157.000000 total_request_latency_count 2 # HELP echo_request_size Echo request size # TYPE echo_request_size histogram echo_request_size_bucket{le="1.000000"}0 echo_request_size_bucket{le="10.000000"}0 echo_request_size_bucket{le="100.000000"}2 echo_request_size_bucket{le="+Inf"} 2 echo_request_size_sum 40.000000 echo_request_size_count 2 ~~~ å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们针对tutorial-02çš„client产生的两个请求,分别获得的四ç§�基本指标的统计数æ�®ã€‚其中histogram是我们创建的,而gaugeã€�counterã€�summary都是æ�’件中自带的。æ�’件中默认统计的数æ�®è¿˜ä¼šé™†ç»­æ·»åŠ ï¼Œä»¥æ–¹ä¾¿å¼€å�‘者。 ### 3. 上报Prometheus的特点 上报Prometheus主è¦�特点刚æ‰�å·²ç»�大概æ��述过: 1. 使用Pull模å¼�,定期被收集; 2. 需è¦�指定我们被收集数æ�®çš„端å�£ï¼› 3. 通过/metrics返回具体数æ�®å†…容; 4. æ•°æ�®å†…容为Prometheus所约定的stringæ ¼å¼�ï¼› 界é�¢å�‚考如下: ![prometheus_img](https://raw.githubusercontent.com/wiki/holmes1412/holmes1412/workflow-prometheus_example.png) ### 4. 上报OpenTelemetry的特点 上报OpenTelemetry的主è¦�特点: 1. 使用推é€�模å¼�,定期å�‘é€�http请求; 2. æ�’件需è¦�å¡«å…¥è¦�上报的URLï¼› 3. 内部默认通过/v1/metrics上报数æ�®ï¼› 4. æ•°æ�®å†…容为OpenTelemetry所约定的[protobuf](/src/module/proto/opentelemetry_metrics.proto)ï¼› 基本接å�£å�‚考: ~~~cpp class RPCMetricsOTel : public RPCMetricsFilter { public: RPCMetricsOTel(const std::string& url); RPCMetricsOTel(const std::string& url, unsigned int redirect_max, unsigned int retry_max, size_t report_threshold, size_t report_interval); ~~~ 用户å�¯ä»¥æŒ‡å®šç´¯è®¡å¤šå°‘个请求上报一次或者累计多久上报一次,默认为累计100个请求或1秒上报一次。 ### 5. Var模å�— 上é�¢å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们æ¯�个请求都会对全局唯一å��称指定的å�˜é‡�进行æ“�作,那么多线程调用时,对å¤�æ�‚的指标类型(比如直方图或者采样)æ“�作会æˆ�为性能瓶颈å�—? 答案是ä¸�会的,因为通过filter获å�–对应指标的接å�£æ˜¯**thread local**的。 SRPC内部引入了线程安全的var结构,æ¯�次获å�–var时调用的接å�£ï¼Œæ‹¿åˆ°çš„都是thread local的指标指针,æ¯�次统计也都是分别收集到本线程,因此多线程情况下的统计ä¸�会造æˆ�对全局的争抢。而上报是异步上报的,在上报被触å�‘的时候,全局会通过expose()挨个把æ¯�个线程中相应的指标reduce()到一起,最å�Žé€šè¿‡å…·ä½“模å�—需è¦�的格å¼�进行上报。 sogou-srpc-ac65014/docs/docs-10-http-with-modules.md000066400000000000000000000301451525217424400221650ustar00rootroot00000000000000 ## 10 - 带生æ€�æ�’ä»¶çš„HttpServerå’ŒHttpClient **srpc**æ��供带有æ�’件功能的**HttpServer**å’Œ**HttpClient**,å�¯ä»¥ä¸ŠæŠ¥**trace**å’Œ**metrcis**,用法和功能完全兼容**Workflow**,添加æ�’件的用法也和srpcç›®å‰�çš„Serverå’ŒClient一样,用于使用**Http功能**å�Œæ—¶éœ€**è¦�采集traceã€�metrics等信æ�¯å¹¶ä¸ŠæŠ¥**的场景。 此功能更加适用于原先已ç»�使用Workflowæ”¶å�‘Http的开å�‘者,几乎ä¸�改动现有代ç �å�³å�¯æ‹¥æœ‰ç”Ÿæ€�上报功能。 (1) 补充æ�’件基本介ç»�: - **链路信æ�¯**:如何使用**trace**æ�’件,并上报到[OpenTelemetry](https://opentelemetry.io)? å�‚考:[docs-08-tracing.md](https://github.com/sogou/srpc/blob/master/docs/docs-08-tracing.md) - **监控指标**:如何使用**metrics**æ�’件,并且上报到[Prometheus](https://prometheus.io/)å’Œ[OpenTelemetry](https://opentelemetry.io)?å�‚考:[docs-09-metrics.md](https://github.com/sogou/srpc/blob/master/docs/docs-09-metrics.md) (2) 补充Workflow中的Http用法: - [Http客户端任务:wget](https://github.com/sogou/workflow/blob/master/docs/tutorial-01-wget.md) - [ttpæœ�务器:http_echo_server](https://github.com/sogou/workflow/blob/master/docs/tutorial-04-http_echo_server.md) (3) 补充SRPC中带æ�’件上报功能的示例代ç �: - [tutorial-17-http_server.cc](/tutorialtutorial-17-http_server.cc) - [tutorial-18-http_client.cc](/tutorialtutorial-18-http_client.cc) ### 1. HttpServer用法 ```cpp int main() { // 1. 构造方å¼�与Workflow类似,Workflow用法是: // WFHttpServer server([](WFHttpTask *task){ /* process */ }); srpc::HttpServer server([](WFHttpTask *task){ /* process */ }); // 2. æ�’件与SRPC通用,添加方å¼�一致 srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318"); server.add_filter(&otel); // 3. serverå�¯åŠ¨æ–¹å¼�与Workflow/SRPC一样 if (server.start(1412) == 0) { ... } } ``` 由于这里直接收å�‘Http请求,因此有几个注æ„�点: 1. æ“�作的是`WFHttpTask`(与Workflow中的一致),而ä¸�是由Protobuf或者Thriftç­‰IDL定义的结构体; 2. 接å�£ä¹Ÿä¸�å†�是RPC里定义的**Service**了,因此也无需派生ServiceImpl实现RPC函数,而是直接给Server传一个`process函数`ï¼› 3. `process函数`æ ¼å¼�也与Workflow一致:`std::function`ï¼› 4. process函数里拿到的å�‚数是task,通过task->get_req()å’Œtask->get_resp()å�¯ä»¥æ‹¿åˆ°è¯·æ±‚与回å¤�,分别是`HttpRequest`å’Œ`HttpResponse`,而其他上下文也在task而é�ž**RPCContext**上; ### 2. HttpClient用法 ```cpp int main() { // 1. 先构造一个client(与Workflow用法ä¸�å�Œï¼Œä¸ŽSRPC用法类似) srpc::HttpClient client; // 2. æ�’件与SRPC通用,添加方å¼�一致 srpc::RPCTraceDefault trace_log; client.add_filter(&trace_log); // 3. å�‘出Http请求的方å¼�与Workflow类似,Workflow用法是: // WFHttpTask *task = WFTaskFactory::create_http_task(...); // 函数å��ã€�å�‚æ•°ã€�返回值与Workflow用法一致 WFHttpTask *task = client.create_http_task("http://127.0.0.1:1412", REDIRECT_MAX, RETRY_MAX, [](WFHttpTask *task){ /* callback */ }); task->start(); ... return 0; } ``` å�Œæ ·å‡ ä¸ªæ³¨æ„�点: 1. æ“�作的是`WFHttpTask`(与Workflow中的一致),而ä¸�是由Protobuf或者Thriftç­‰IDL定义的结构; 2. 如果想拿到req,å�¯ä»¥`task->get_req()`,拿到的是`HttpRequest`,而ä¸�是XXXRequest之类; 3. 和请求相关的上下文也ä¸�å†�需è¦�**RPCContext**了,都在task上; 4. callback函数格å¼�也与Workflow一致:`std::function`ï¼› ### 3. Httpå��议采集的数æ�® **OpenTelemetryçš„traceæ•°æ�®å®˜æ–¹æ–‡æ¡£**: https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/http/ SRPC框架的trace模å�—å·²ç»�采集以下内容,并会通过å�„自的filter以ä¸�å�Œçš„å½¢å¼�å�‘出。 **1. 公共指标** | 指标å�� | å�«ä¹‰ | 类型 | 例å­� | 备注 | |-------|-----|-----|------|-----| |task.state| 框架状æ€�ç � | int | 0 |(以下简称state) | |task.error| 框架错误ç � | int | 1 |state!=0æ—¶æ‰�有 | |http.status_code| Http返回ç � | string | 200 | state=0æ—¶æ‰�有 | |http.method| Http请求方法 | string | GET | | |http.scheme| scheme | string | https | | |http.request_content_length | 请求大å°� | int | 3840 | state=0æ—¶æ‰�有 | |http.response_content_length| 回å¤�大å°� | int | 141 | state=0æ—¶æ‰�有 | |net.sock.family| å��议地å�€æ—� | string | inet | | |net.sock.peer.addr| 远程地å�€ | string | 10.xx.xx.xx | state=0æ—¶æ‰�有 | |net.sock.peer.port| 远程端å�£ | int | 8080 | state=0æ—¶æ‰�有 | **2. Client指标** | 指标å�� | å�«ä¹‰ | 类型 | 例å­� | 备注 | |-------|-----|-----|------|-----| |srpc.timeout_reason| 超时原因 | int | 2 | state=WFT_STATE_SYS_ERROR(1)å’Œerror=ETIMEDOUT(116)æ—¶æ‰�有 | |http.resend_count| 框架é‡�试次数 | int | 0 | state=0æ—¶æ‰�有 | |net.peer.addr| uri请求的地å�€ | string | 10.xx.xx.xx | | |net.peer.port| uri请求的端å�£ | int | 80 | | |* http.url| 请求的url | string | | | state与errorå�‚考:[workflow/src/kernel/Communicator.h](workflow/src/kernel/Communicator.h) timeout_reasonå�‚考:[workflow/src/kernel/CommRequest.h](workflow/src/kernel/CommRequest.h) **3. Server指标** | 指标å�� | å�«ä¹‰ | 类型 | 例å­� | 备注 | |-------|-----|-----|------|-----| |net.host.name| æœ�务器å��ç§° | string | example.com | 虚拟主机å��,æ�¥è‡ªæ¯”如对方header里å�‘过æ�¥çš„Hostä¿¡æ�¯ç­‰ | |net.host.port| 监å�¬çš„端å�£ | int | 80 | | |http.target| 完整的请求目标 | string | /users/12314/?q=ddds | | |http.client_ip| 原始client地å�€ | string | 10.x.x.x | 有时候会有,比如转å�‘场景下,header中带有“X-Forwarded-Forâ€�ç­‰ | **OpenTelemetryçš„metricsæ•°æ�®å®˜æ–¹æ–‡æ¡£ï¼ˆSRPC正在支æŒ�中)**: https://opentelemetry.io/docs/reference/specification/metrics/semantic_conventions/http-metrics/ ### 4. 示例数æ�® 以下通过client-server的调用,我们å�¯ä»¥çœ‹åˆ°è¿™æ ·çš„链路图: ``` [trace_id : 005028aa52fb0000005028aa52fb0002] [client]->[server] timeline: 1681380123462593000 1681380123462895000 1681380123463045000 1681380123463213000 [client][begin].........................................................................[end] span_id : 0400fb52aa285000 [server][begin].....................[end] span_id : 00305c54aa285000 parent_span_id : 0400fb52aa285000 ``` 先通过`make tutorial`命令å�¯ä»¥æŠŠtutorial里的`http_server`å’Œ`http_client`编出æ�¥ï¼Œå…¶ä¸­tutorial-17-http_server.cc中å�¯ä»¥æŠŠä¸ŠæŠ¥OpenTelemetryçš„æ�’件打开: ```cpp srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318"); srpc::HttpServer server(process); server.add_filter(&otel); ... ``` 分别按照如下执行,å�¯ä»¥çœ‹åˆ°æˆ‘们的client通过RPCTraceDefaultæ�’件本地打å�°çš„traceä¿¡æ�¯ï¼š ```sh ./http_client ``` ``` callback. state = 0 error = 0 Hello from server! finish print body. body_len = 31 [SPAN_LOG] trace_id: 005028aa52fb0000005028aa52fb0002 span_id: 0400fb52aa285000 start_time: 1681380123462593000 finish_time: 1681380123463213000 duration: 620000(ns) http.method: GET http.request_content_length: 123145504128464 http.resend_count: 0 http.response_content_length: 31 http.scheme: http http.status_code: 200 net.peer.name: 127.0.0.1 net.peer.port: 1412 net.sock.family: inet net.sock.peer.addr: 127.0.0.1 net.sock.peer.port: 1412 component: srpc.srpc span.kind: srpc.client state: 0 ^C ``` 我们把server对OpenTelemetry上报的Protobuf内容也å�Œæ—¶æ‰“å�°å‡ºæ�¥ï¼š ```sh ./http_server ``` ```sh http server get request_uri: / [SPAN_LOG] trace_id: 005028aa52fb0000005028aa52fb0002 span_id: 00305c54aa285000 parent_span_id: 0400fb52aa285000 start_time: 1681380123462895000 finish_time: 1681380123463045000 duration: 150000(ns) http.method: GET http.request_content_length: 0 http.response_content_length: 31 http.scheme: http http.status_code: 200 http.target: / net.host.name: 127.0.0.1:1412 net.host.port: 1412 net.sock.family: inet net.sock.peer.addr: 127.0.0.1 net.sock.peer.port: 56591 component: srpc.srpc span.kind: srpc.server state: 0 resource_spans { resource { } instrumentation_library_spans { spans { trace_id: "\000P(\252R\373\000\000\000P(\252R\373\000\002" span_id: "\0000\\T\252(P\000" parent_span_id: "\004\000\373R\252(P\000" name: "GET" kind: SPAN_KIND_SERVER start_time_unix_nano: 1681380123462895000 end_time_unix_nano: 1681380123463045000 attributes { key: "http.method" value { string_value: "GET" } } attributes { key: "http.request_content_length" value { int_value: 0 } } attributes { key: "http.response_content_length" value { int_value: 31 } } attributes { key: "http.scheme" value { string_value: "http" } } attributes { key: "http.status_code" value { int_value: 200 } } attributes { key: "http.target" value { string_value: "/" } } attributes { key: "net.host.name" value { string_value: "127.0.0.1:1412" } } attributes { key: "net.host.port" value { int_value: 1412 } } attributes { key: "net.sock.family" value { string_value: "inet" } } attributes { key: "net.sock.peer.addr" value { string_value: "127.0.0.1" } } attributes { key: "net.sock.peer.port" value { int_value: 56591 } } status { } } } } ``` å�¯ä»¥çœ‹åˆ°ç”¨æ³•å’ŒSRPC框架默认的trace模å�—是一样的。 ### 5. å…¶ä»– 补充一些常è§�问题,帮助开å�‘中更好地ç�†è§£è¿™ä¸ªæ¨¡å�—。 **Q1: 本次新增的HttpServer与HttpClient,与SRPCHttpServer/SRPCHttpClient有什么共å�Œç‚¹/ä¸�å�Œç‚¹ï¼Ÿ** å…±å�Œç‚¹ï¼š - 应用层å��议相å�Œï¼Œéƒ½æ˜¯Httpå��议进行网络收å�‘ï¼› - 采集的数æ�®ä¾�æ�®ç›¸å�Œï¼Œéƒ½æ˜¯ä»ŽHttp Header收集和é€�传出去; ä¸�å�Œç‚¹ï¼š - 用法ä¸�å�Œï¼šæœ¬æ¬¡æ–°å¢žçš„Http模å�—是延续Workflow风格的用法,比如WFHttpTask;而SRPCHttp/ThriftHttp/TRPCHttp的用法是RPC模å¼�,且包括了å�Œæ­¥/异步/å�Šå�Œæ­¥çš„使用方å¼�ï¼› - 接å�£ä¸�å�Œï¼šå‰�者用url直接定ä½�è¦�å�‘的请求,而server也是一个process函数作为处ç�†è¯·æ±‚的统一入å�£ï¼›è€ŒåŽŸå…ˆçš„æ¨¡å�—对Httpå�ªæ˜¯ç½‘络层é�¢çš„æ”¶å�‘,url中的路由信æ�¯æ˜¯é€šè¿‡${service}å’Œ${method}进行拼接的,然å�ŽæŠŠProtobuf或者Thrift这个结构体作为Httpå��议的bodyå�‘出; - å¼€å�‘者接触的请求/回å¤�ä¸�å�Œï¼šå‰�者从task上拿出HttpRequestå’ŒHttpResponse,å�Žè€…是Protobuf/Thrift里定义的Messageï¼› - 框架级stateå’Œerror略有ä¸�å�Œï¼šå‰�者是task.stateå’Œtask.error,使用workflow的状æ€�ç �,比如0表示æˆ�功;而å�Žè€…是srpc.stateå’Œsrpc.error,使用SRPCçš„[状æ€�ç �](/src/rpc_basic.h),比如1表示æˆ�功; **Q2: å’ŒWorkflow原生的Httpå��议是什么关系?** srpc中新增的功能,开å�‘者拿到的也是Workflow中定义的WFHttpTask,在实现上进行了行为派生,因此收å�‘期间有几个切é�¢å�¯ä»¥è¿›è¡Œä¸€äº›æ¨¡å�—化编程,这是和Workflowçš„Http相比更多的功能。 å�ªè¦�å¼€å�‘者把模å�—通过add_filter()加入到Server/Client中,通过产生的任务就是带有切é�¢åŠŸèƒ½çš„ï¼Œè€Œæœ�务开å�‘者无需感知。 **Q3: 为什么放在SRPC项目中?** ç›®å‰�生æ€�æ�’件所需è¦�的几个功能都在SRPC项目中,包括: - 收集数æ�®çš„rpc_module,包括生æˆ�trace_id等通用功能; - 上报信æ�¯çš„rpc_filter,包括使用Protobufæ ¼å¼�上报OpenTelemetryï¼› - 统计监控指标的rpc_var; sogou-srpc-ac65014/docs/en/000077500000000000000000000000001525217424400155005ustar00rootroot00000000000000sogou-srpc-ac65014/docs/en/docs-01-idl.md000066400000000000000000000014371525217424400177430ustar00rootroot00000000000000[中文版](/docs/docs-01-idl.md) ## 01 - RPC IDL - Interface Description Languaue file - Backward and forward compatibility - Protobuf/Thrift ### Sample You can follow the detailed example below: - Take pb as an example. First, define an `example.proto` file with the ServiceName as `Example`. - The name of the rpc interface is `Echo`, with the input parameter as `EchoRequest`, and the output parameter as `EchoResponse`. - `EchoRequest` consists of two strings: `message` and `name`. - `EchoResponse` consists of one string: `message`. ~~~proto syntax="proto2"; message EchoRequest { optional string message = 1; optional string name = 2; }; message EchoResponse { optional string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ sogou-srpc-ac65014/docs/en/docs-02-service.md000066400000000000000000000023351525217424400206320ustar00rootroot00000000000000[中文版](/docs/docs-02-service.md) ## 02 - RPC Service - It is the basic unit for SRPC services. - Each service must be generated by one type of IDLs. - Service is determined by IDL type, not by specific network communication protocol. ### Sample You can follow the detailed example below: - Use the same `example.proto` IDL above. - Run the official `protoc example.proto --cpp_out=./ --proto_path=./` to get two files: `example.pb.h` and `example.pb.cpp`. - Run the `srpc_generator protobuf ./example.proto ./` in SRPC to get `example.srpc.h`. - Derive `Example::Service` to implement the rpc business logic, which is an RPC Service. - Please note that this Service does not involve any concepts such as network, port, communication protocol, etc., and it is only responsible for completing the business logic that convert `EchoRequest` to `EchoResponse`. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; ~~~ sogou-srpc-ac65014/docs/en/docs-03-server.md000066400000000000000000000051211525217424400204750ustar00rootroot00000000000000[中文版](/docs/docs-03-server.md) ## 03 - RPC Server - Each server corresponds to one port - Each server corresponds to one specific network communication protocol - One service may be added into multiple Servers - One Server may have one or more Services, but the ServiceName must be unique within that Server - Services from different IDLs can be added into the same Server ### Sample You can follow the detailed example below: - Follow the above `ExampleServiceImpl` Service - First, create an RPC Server and determine the proto file. - Then, create any number of Service instances and any number of Services for different protocols, and add these services to the Server through the `add_service()`interface. - Finally, use `start()` or `serve()` to start the services in the Server and handle the upcoming rpc requests through the Server. - Imagine that we can also derive more Serivce from `Example::Service`, which have different implementations of rpc `Echo`. - Imagine that we can create N different RPC Servers on N different ports, serving on different network protocols. - Imagine that we can use `add_service()` to add the same ServiceIMPL instance on different Servers, or we can use `add_service()` to add different ServiceIMPL instances on the same server. - Imagine that we can use the same `ExampleServiceImpl`, serving BRPC-STD, SRPC-STD, SRPC-Http at three different ports at the same time. - And we can use `add_service()` to add one `ExampleServiceImpl` related to Protobuf IDL and one `AnotherThriftServiceImpl` related to Thrift IDL to the same SRPC-STD Server, and the two IDLs work perfectly on the same port! ~~~cpp int main() { SRPCServer server_srpc; SRPCHttpServer server_srpc_http; BRPCServer server_brpc; ThriftServer server_thrift; TRPCServer server_trpc; TRPCHttpServer server_trpc_http; ExampleServiceImpl impl_pb; AnotherThriftServiceImpl impl_thrift; server_srpc.add_service(&impl_pb); server_srpc.add_service(&impl_thrift); server_srpc_http.add_service(&impl_pb); server_srpc_http.add_service(&impl_thrift); server_brpc.add_service(&impl_pb); server_thrift.add_service(&impl_thrift); server_trpc.add_service(&impl_pb); server_trpc_http.add_service(&impl_pb); server_srpc.start(1412); server_srpc_http.start(8811); server_brpc.start(2020); server_thrift.start(9090); server_trpc.start(2022); server_trpc_http.start(8822); getchar(); server_trpc_http.stop(); server_trpc.stop(); server_thrift.stop(); server_brpc.stop(); server_srpc_http.stop(); server_srpc.stop(); return 0; } ~~~ sogou-srpc-ac65014/docs/en/docs-04-client.md000066400000000000000000000043761525217424400204610ustar00rootroot00000000000000[中文版](/docs/docs-04-client.md) ## 04 - RPC Client - Each Client corresponds to one specific target/one specific cluster - Each Client corresponds to one specific network communication protocol - Each Client corresponds to one specific IDL ### Sample You can follow the detailed example below: - Following the above example, the client is relatively simple and you can call the method directly. - Use `Example::XXXClient` to create a client instance of some RPC. The IP+port or URL of the target is required. - With the client instance, directly call the rpc function `Echo`. This is an asynchronous request, and the callback function will be invoked after the request is completed. - For the usage of the RPC Context, please check [RPC Context](/docs/en/rpc.md#rpc-context). ~~~cpp #include #include "example.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello!"); req.set_name("SRPCClient"); WFFacilities::WaitGroup wait_group(1); client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } ~~~ ### Client startup parameters Client can be started directly by passing in ip, port, or through client startup parameters. The above example: ~~~cpp Example::SRPCClient client("127.0.0.1", 1412); ~~~ is equivalent to: ~~~cpp struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT; param.host = "127.0.0.1"; param.port = 1412; Example::SRPCClient client(¶m); ~~~ also equivalent to: ~~~cpp struct RPCClientParams param = RPC_CLIENT_PARAMS_DEFAULT; param.url = "srpc://127.0.0.1:1412"; Example::SRPCClient client(¶m); ~~~ Note that `RPC_CLIENT_PARAMS_DEFAULT` must be used to initialize the client's parameters, which contains a `RPCTaskParams`, including the default data_type, compress_type, retry_max and various timeouts. The specific struct can refer to [rpc_options.h](/src/rpc_options.h). sogou-srpc-ac65014/docs/en/docs-05-context.md000066400000000000000000000064021525217424400206600ustar00rootroot00000000000000[中文版](/docs/docs-05-context.md) ## 05 - RPC Context - RPCContext is used specially to assist asynchronous interfaces, and can be used in both Service and Client. - Each asynchronous interface will provide a Context, which offers higher-level functions, such as obtaining the remote IP, the connection seqid, and so on. - Some functions on Context are unique to Server or Client. For example, you can set the compression mode of the response data on Server, and you can obtain the success or failure status of a request on Client. - On the Context, you can use ``get_series()`` to obtain the SeriesWork, which is seamlessly integrated with the asynchronous mode of Workflow. ### RPCContext API - Common #### `long long get_seqid() const;` One complete communication consists of request+response. The sequence id of the communication on the current socket connection can be obtained, and seqid=0 indicates the first communication. #### `std::string get_remote_ip() const;` Get the remote IP address. IPv4/IPv6 is supported. #### `int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;` Get the remote address. The in/out parameter is the lower-level data structure sockaddr. #### `const std::string& get_service_name() const;` Get RPC Service Name. #### `const std::string& get_method_name() const;` Get RPC Method Name. #### `SeriesWork *get_series() const;` Get the SeriesWork of the current ServerTask/ClientTask. ### RPCContext API - Only for client done #### `bool success() const;` For client only. The success or failure of the request. #### `int get_status_code() const;` For client only. The rpc status code of the request. #### `const char *get_errmsg() const;` For client only. The error info of the request. #### `int get_error() const;` For client only. The error code of the request. #### `void *get_user_data() const;` For client only. Get the user\_data of the ClientTask. If a user generates a task through the ``create_xxx_task()`` interface, the context can be recorded in the user_data field. You can set that field when creating the task, and retrieve it in the callback function. ### RPCContext API - Only for server process #### `void set_data_type(RPCDataType type);` For Server only. Set the data packaging type - RPCDataProtobuf - RPCDataThrift - RPCDataJson #### `void set_compress_type(RPCCompressType type);` For Server only. Set the data compression type (note: the compression type for the Client is set on Client or Task) - RPCCompressNone - RPCCompressSnappy - RPCCompressGzip - RPCCompressZlib - RPCCompressLz4 #### `void set_attachment_nocopy(const char *attachment, size_t len);` For Server only. Set the attachment. #### `bool get_attachment(const char **attachment, size_t *len) const;` For Server only. Get the attachment. #### `void set_reply_callback(std::function cb);` For Server only. Set reply callback, which is called after the operating system successfully writes the data into the socket buffer. #### `void set_send_timeout(int timeout);` For Server only. Set the maximum time for sending the message, in milliseconds. -1 indicates unlimited time. #### `void set_keep_alive(int timeout);` For Server only. Set the maximum connection keep-alive time, in milliseconds. -1 indicates unlimited time. sogou-srpc-ac65014/docs/en/docs-06-workflow.md000066400000000000000000000135061525217424400210520ustar00rootroot00000000000000[中文版](/docs/docs-06-workflow.md) ## 06 - Integrating with the asynchronous Workflow framework ### 1. Server You can follow the detailed example below: - Echo RPC sends an HTTP request to the upstream modules when it receives the request. - After the request to the upstream modules is completed, the server populates the body of HTTP response into the message of the response and send a reply to the client. - We don't want to block/occupy the handler thread, so the request to the upstream must be asynchronous. - First, we can use `WFTaskFactory::create_http_task()` of the factory of Workflow to create an asynchronous http_task. - Then, we use `ctx->get_series()` of the RPCContext to get the SeriesWork of the current ServerTask. - Finally, we use the `push_back()` interface of the SeriesWork to append the http\_task to the SeriesWork. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [request, response](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); response->mutable_message()->assign((const char *)data, len); } else response->set_message("Error: " + std::to_string(task->get_error())); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); }); ctx->get_series()->push_back(http_task); } }; ~~~ ### 2. Client You can follow the detailed example below: - We send two requests in parallel. One is an RPC request and the other is an HTTP request. - After both requests are finished, we initiate a calculation task again to calculate the sum of the squares of the two numbers. - First, use `create_Echo_task()` of the RPC Client to create an rpc\_task, which is an asynchronous RPC network request. - Then, use `WFTaskFactory::create_http_task` and `WFTaskFactory::create_go_task` in the the factory of Workflow to create an asynchronous network task http\_task and an asynchronous computing task calc\_task respectively. - Finally, use the serial-parallel graph to organize three asynchronous tasks, in which the multiplication sign indicates parallel tasks and the greater than sign indicates serial tasks and then execute ``start()``. ~~~cpp void calc(int x, int y) { int z = x * x + y * y; printf("calc result: %d\n", z); } int main() { Example::SRPCClient client("127.0.0.1", 1412); auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { std::string body; const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); body.assign((const char *)data, len); printf("%s\n\n", body.c_str()); } else printf("Http request fail\n\n"); }); auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4); EchoRequest req; req.set_message("Hello!"); req.set_name("1412"); rpc_task->serialize_input(&req); WFFacilities::WaitGroup wait_group(1); SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) { wait_group.done(); }); series->push_back(rpc_task); series->push_back(calc_task); series->start(); wait_group.wait(); return 0; } ~~~ ### 3. Upstream SRPC can directly use any component of Workflow, the most commonly used is [Upstream](https://github.com/sogou/workflow/blob/master/docs/en/about-upstream.md), any kind of client of SRPC can use Upstream. You may use the example below to construct a client that can use Upstream through parameters: ```cpp #include "workflow/UpstreamManager.h" int main() { // 1. create upstream and add server instances UpstreamManager::upstream_create_weighted_random("echo_server", true); UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412"); UpstreamManager::upstream_add_server("echo_server", "192.168.10.10"); UpstreamManager::upstream_add_server("echo_server", "internal.host.com"); // 2. create params and fill upstream name RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.host = "echo_server"; client_params.port = 1412; // this port only used when upstream URI parsing and will not affect the select of instances // 3. construct client by params, the rest of usage is similar as other tutorials Example::SRPCClient client(&client_params); ... ``` If we use the **ConsistentHash** or **Manual** upstream, we often need to distinguish different tasks for the selection algorithm. At this time, we may use the `int set_uri_fragment(const std::string& fragment);` interface on the client task to set request-level related information. This field is the fragment in the URI. For the semantics, please refer to [RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5), any infomation that needs to use the fragment (such as other information included in some other selection policy), you may use this field as well. sogou-srpc-ac65014/docs/en/docs-07-srpc-http.md000066400000000000000000000162331525217424400211250ustar00rootroot00000000000000[中文版](/docs/docs-07-srpc-http.md) ## 07 - Use Http with SRPC, TRPC and Thrift **srpc** supports **HTTP** protocol, which make it convenient to communicate to other language. Just fill the **IDL** content into **HTTP body**, and fill the **IDL** type(**json**/**protobuf**/**thrift**) into **Http header** , we communicate with other frameworks through the **HTTP** protocol. - **SRPCHttpServer**, **TRPCHttpServer** and **ThriftHttpServer** can receive HTTP requests from client implemented by any language. - **SRPCHttpClient**, **TRPCHttpClient** and **ThriftHttpClient** can send HTTP requests to servers implemented by any language. - **HTTP header**: `Content-Type` needs to be set as `application/json` when body is json, or set as `application/x-protobuf` when body is protobuf, or set as `application/x-thrift` when body is thrift; - **HTTP body**: If there is **bytes** type in body, **json** needs to be encoded with **base64**. ### 1. Example To implement **SRPCHttpClient** is so simple that we just need to refer to [tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc) or [tutorial-09-client_task.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-09-client_task.cc) and change the `SRPCClient` into `SRPCHttpClient`. [README.md](/docs/en/README.md#6-run) demonstrates how to use **curl** to send a request to **SRPCHttpServer**, and below we give an example to demonstrate how to use **python** as a client to send a request to **TRPCHttpServer**. **proto file :** ```proto syntax="proto3"; // proto2 or proto3 are both supported package trpc.test.helloworld; message AddRequest { string message = 1; string name = 2; bytes info = 3; int32 error = 4; }; message AddResponse { string message = 1; }; service Batch { rpc Add(AddRequest) returns (AddResponse); }; ``` **python client :** ```py import json import requests from base64 import b64encode class Base64Encoder(json.JSONEncoder): def default(self, o): if isinstance(o, bytes): return b64encode(o).decode() return json.JSONEncoder.default(self, o) headers = {'Content-Type': 'application/json'} req = { 'message': 'hello', 'name': 'k', 'info': b'i am binary' } print(json.dumps(req, cls=Base64Encoder)) ret = requests.post(url = "http://localhost:8800/trpc.test.helloworld.Batch/Add", headers = headers, data = json.dumps(req, cls=Base64Encoder)) print(ret.json()) ``` ### 2. Splicing the path of HTTP request [README.md](/docs/en/README_cn.md#6-run) shows that the path is concatenated by the service name and the rpc name. Moreover, for the above proto file example with the package name `package trpc.test.helloworld;`, the package name also needs to be spliced into the path. The splicing paths of **SRPCHttp** and **TRPCHttpClient** are different. For **ThriftHttp**, multi service is not supported in SRPC thrift, so no path is required. Let's take **curl** as an example: Request to **SRPCHttpServer**: ```sh curl 127.0.0.1:8811/trpc/test/helloworld/Batch/Add -H 'Content-Type: application/json' -d '{...}' ``` Request to **TRPCHttpServer**: ```sh curl 127.0.0.1:8811/trpc.test.helloworld.Batch/Add -H 'Content-Type: application/json' -d '{...}' ``` Request to **ThriftHttpServer**: ```sh curl 127.0.0.1:8811 -H 'Content-Type: application/json' -d '{...}' ``` ### 3. HTTP status code SRPC supports server to set HTTP status code in `process()`, the interface is `set_http_code(int code)` on **RPCContext**. This error code is only valid if the framework can handle the request correctly, otherwise it will be set to the SRPC framework-level error code. **Usage :** ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { if (req->name() != "workflow") ctx->set_http_code(404); // set HTTP status code as 404 else resp->set_message("Hi back"); } }; ~~~ **CURL command :** ~~~sh curl -i 127.0.0.1:1412/Example/Echo -H 'Content-Type: application/json' -d '{message:"from curl",name:"CURL"}' ~~~ **Result :** ~~~sh HTTP/1.1 404 Not Found SRPC-Status: 1 SRPC-Error: 0 Content-Type: application/json Content-Encoding: identity Content-Length: 21 Connection: Keep-Alive ~~~ **Notice :** We can still distinguish this request is correct at framework-level by `SRPC-Status: 1` in the header of the returned result, and `404` is the status code from the server. ### 4. HTTP Header Use the following three interfaces to get or set HTTP header: ~~~cpp bool get_http_header(const std::string& name, std::string& value) const; bool set_http_header(const std::string& name, const std::string& value); bool add_http_header(const std::string& name, const std::string& value); ~~~ For **server**, these interfaces are on `RPCContext`. For **client**, we need to set the **HTTP header on the request** through `RPCClientTask`, and get the **HTTP header on the response** on the `RPCContext` of the callback function. The usage is as follows: ~~~cpp int main() { Example::SRPCHttpClient client("127.0.0.1", 80); EchoRequest req; req.set_message("Hello, srpc!"); auto *task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) { std::string value; ctx->get_http_header("server_key", value); // get the HTTP header on response } }); task->serialize_input(&req); task->set_http_header("client_key", "client_value"); // set the HTTP headers on request task->start(); wait_group.wait(); return 0; } ~~~ ### 5. proto3 transport format problem When IDL is protobuf, by default proto3 primitive fields with default values will be omitted in JSON output because there is no **optional** and **required** distinction. For example, an int32 field set to 0 will be omitted.So proto3 cannot be recognized as being set, and it cannot be emitted when serialized. JsonPrintOptions can use a flag to override the default behavior and print primitive fields regardless of their values. SRPC provides some interfaces on **RPCContext上** to implement this and other flags on [JsonPrintOptions](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.util.json_util#JsonPrintOptions). The specific interface and usage description can be refered in:[rpc_context.h](/src/rpc_context.h) **Exmaple :** ```cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); resp->set_error(0); // the type of error is int32 and 0 is the default value of int32 ctx->set_json_always_print_fields_with_no_presence(true); // all fields with no precense ctx->set_json_add_whitespace(true); // add spaces, line breaks and indentation } }; ``` **Origin output :** ```sh {"message":"Hi back"} ``` **After set json options on RPCContext:** ```sh { "message": "Hi back", "error": 0 } ``` sogou-srpc-ac65014/docs/en/docs-08-tracing.md000066400000000000000000000111331525217424400206230ustar00rootroot00000000000000[中文版](/docs/docs-08-tracing.md) ## 08 - Report Tracing to OpenTelemetry **SRPC** supports generating and reporting tracing and spans, which can be reported in multiple ways, including exporting data locally or to [OpenTelemetry](https://opentelemetry.io). Since **SRPC** follows the [data specification](https://github.com/open-telemetry/opentelemetry-specification) of **OpenTelemetry** and the specification of [w3c trace context](https://www.w3.org/TR/trace-context/), now we can use **RPCTraceOpenTelemetry** as the reporting plugin. The report conforms to the **Workflow** style, which is pure asynchronous task and therefore has no performance impact on the RPC requests and services. ### 1. Usage After the plugin `RPCTraceOpenTelemetry` is constructed, we can use `add_filter()` to add it into **server** or **client**. For [tutorial-02-srpc_pb_client.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-02-srpc_pb_client.cc), add 2 lines like the following : ```cpp int main() { Example::SRPCClient client("127.0.0.1", 1412); RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318"); client.add_filter(&span_otel); ... } ``` For [tutorial-01-srpc_pb_server.cc](https://github.com/sogou/srpc/blob/master/tutorial/tutorial-01-srpc_pb_server.cc), add the similar 2 lines. We also add the local plugin to print the reported data on the screen : ```cpp int main() { SRPCServer server; RPCTraceOpenTelemetry span_otel("http://127.0.0.1:4318"); server.add_filter(&span_otel); RPCTraceDefault span_log; // this plugin will print the tracing info on the screen server.add_filter(&span_log); ... } ``` make the tutorial and run both server and client, we can see some tracing information on the screen. image We can find the span_id: **04d070f537f17d00** in client become parent_span_id: **04d070f537f17d00** in server: image ### 2. Traces on Jaeger Open the show page of Jaeger, we can find our service name **Example** and method name **Echo**. Here are two span nodes, which were reported by server and client respectively. image As what we saw on the screen, the client reported span_id: **04d070f537f17d00** and server reported span_id: **00202cf737f17d00**, these span and the correlated tracing information can be found on Jaeger, too. image ### 3. About Parameters How long to collect a trace, and the number of reported retries and other parameters can be specified through the constructor parameters of `RPCTraceOpenTelemetry`. Code reference: [src/module/rpc_trace_filter.h](https://github.com/sogou/srpc/blob/master/src/module/rpc_trace_filter.h#L238) The default value is to collect up to 1000 trace information per second, and features such as transferring tracing information through the srpc framework transparently have also been implemented, which also conform to the specifications. ### 4. Attributes We can also use `add_attributes()` to add some other informations as OTEL_RESOURCE_ATTRIBUTES. Please notice that our service name "Example" is set also thought this attributes, the key of which is `service.name`. If `service.name` is also provided in OTEL_RESOURCE_ATTRIBUTES by users, then srpc service name takes precedence. Refers to : [OpenTelemetry#resource](https://github.com/open-telemetry/opentelemetry-dotnet/tree/main/src/OpenTelemetry#resource) ### 5. Log and Baggage SRPC provides `log()` and `baggage()` to carry some user data through span. API : ```cpp void log(const RPCLogVector& fields); void baggage(const std::string& key, const std::string& value); ``` As a server, we can use `RPCContext` to add log annotation: ```cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); ctx->log({{"event", "info"}, {"message", "rpc server echo() end."}}); } }; ``` As a client, we can use `RPCClientTask` to add log on span: ```cpp srpc::SRPCClientTask *task = client.create_Echo_task(...); task->log({{"event", "info"}, {"message", "log by rpc client echo()."}}); ``` sogou-srpc-ac65014/docs/en/docs-09-metrics.md000066400000000000000000000276101525217424400206520ustar00rootroot00000000000000[中文版](/docs/docs-09-metrics.md) ## 09 - Report Metrics to OpenTelemetry / Prometheus **Metrics** are common monitoring requirements. **SRPC** supports the generation and statistics of Metrics, and reports through various way, including reporting to [Prometheus](https://prometheus.io/) and [OpenTelemetry](https://opentelemetry.io). The report conforms to the **Workflow** style, which is pure asynchronous task or pull mode, and therefore has no performance impact on the RPC requests and services. This document will introduce the concept of Metrics, explain the interface of [tutorial-16](/tutorial/tutorial-16-server_with_metrics.cc), the usage of reporting to Prometheus, the usage of reporting to OpenTelemetry, and introduce the Var module which uses thread local to speed up performance. ### 1. Introduction The metrics type of **Prometheus** can be refered through the official documentation : [Concepts - Metrics](https://prometheus.io/docs/concepts/metric_types/) and [Type of Metrics](https://prometheus.io/docs/tutorials/understanding_metric_types/)。 The basic metrics concept of **OpenTelemetry** can be refer through [(data specification)](https://github.com/open-telemetry/opentelemetry-specification) and [Metricsçš„datamodel.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/datamodel.md). The concepts of the four basic metrics are the same, so SRPC provides the most basic support for these four metrics, which can correspond to the reported data of Prometheus and OpenTelemetry respectively. Please refer to the table below: |Metrics type|Prometheus|OpenTelemetry| create api in SRPC | some operations in SRPC | |-------|----------|------------|-----------------|-------------------| |single value| Gauge | Gauge | create_gauge(name, help); | void increase(); void decrease();| |counter| Counter | Sum | create_counter(name, help); |GaugeVar *add(labels);| |histogram |Histogram | Histogram | create_histogram(name, help, buckets); |void observe(data);| |samples | Summary | Summary | create_summary(name, help, quantiles); |void observe(data);| Basic description of the four types of metrics: 1. **Single value**: A simple value that can be increased or decreased. 2. **Counter**: A counter is a cumulative metrics, which can be added several **labels** to distinguish the values under different labels. 3. **Histogram**: A histogram samples observations (usually things like request durations or response sizes) and counts them in configurable buckets, so it is necessary to pass in **buckets** to inform the interval to be divided. For example, if the input bucket value is { 1, 10, 100 }, then we can get the data distributed in these four intervals { 0 - 1, 0 - 10, 0 - 100, 0 - +Inf }. It also provides a sum and the total count of all observed values. 4. **Samples**: Similar to histogram, but **quantiles** are passed in, such as {0.5, 0.9} and their precision. It mainly used in scenarios where the specific value of the data distribution is not known in advance (In contrast, histograms require specific values to be passed in). Sampling comes with a **time window**. You may specify the statistics window by **max_age** and the number of buckets by **age_bucket**. The default in SRPC is using 5 bucket in 60 seconds to switch times window. Considering the statistical complexity of **sampling Summary**, **Histogram** is recommended in preference instead. For convenience, these four type of metrics in SRPC are of type **double**. ### 2. Usage The example shows usage via [tutorial-16-server_with_metrics.cc](/tutorial/tutorial-16-server_with_metrics.cc). Althought this example is for the server, the usage on the client is the same. #### (1) create filter Here we use Prometheus to be reported, thus we need the filter **RPCMetricsPull** as the plugin. ~~~cpp #include "srpc/rpc_metrics_filter.h" // metrics filter header file int main() { SRPCServer server; ExampleServiceImpl impl; RPCMetricsPull filter; // create a filter filter.init(8080); // the port for Prometheus to pull metrics data ~~~ #### (2) add metrics By default, there are some common metrics in the filter **RPCMetricsPull**, including the number of overall request, the number of requests in different service or method as labels, and the quantiles of latency. Users can add any metrics they want. Here we add a histogram namee "echo_request_size" to count the size of requests. This metric describes the information as "Echo request size", and the data bucket division is { 1, 10, 100 }. ~~~cpp filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100}); ~~~ More illutrations: **Timing to add metrics** Metrics can be added any time, even after the server/client has been running. But **must be added before we use** this metrics, otherwise a NULL pointer will be obtained when we acquire the metrics by name. **The name of the metrics** The name of metrics is **globally unique** (no matter which of the four basic types), and **can only contain uppercase and lowercase letters and underscores, ie a~z, A~Z, _** . The creation will fail and return NULL if we create with an existed metric‘s name. We will use this unique name to operate the metrics after it has been created. **Other APIs to create metrics** You can check this header file [rpc_metrics_filter.h](/src/module/rpc_metrics_filter.h): `class RPCMetricsFilter`. #### (3) add filter into server / client The following example keeps the pointer of this filter in ExampleServiceImpl because we may need the APIs on it to operate our metrics. It`s just one of the sample usages and users may use it in the way you used to. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) overrideï¼› void set_filter(RPCMetricsPull *filter) { this->filter = filter; } private: RPCMetricsPull *filter; // keep the pointer of filter and add set_filter(), notice that it's not APIs of SRPC }; ~~~ The usage in main: ~~~cpp int main() { ... impl.set_filter(&filter); // keep the filter throught the APIs in service we mentioned above server.add_filter(&filter); // client can also call add_filter() server.add_service(&impl); filter.deinit(); return 0; } ~~~ #### (4) operation with the metrics Every time we receive a request, we get cumulative calculation of the size of the EchoRequest with the histogram we just created. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong()); } ~~~ As has been shown, we use the API `histogram()` on filter to get the histogram metrics by it's name. Then we use `observe()` to fill in the data size. Let's take a look at the APIs to acquire four types of metrics. ~~~cpp class RpcMetricsFilter : public RPCFilter { GaugeVar *gauge(const std::string& name); CounterVar *counter(const std::string& name); HistogramVar *histogram(const std::string& name); SummaryVar *summary(const std::string& name); ~~~ If the metric is found by this name, the relevant type of pointer will be returned and we may use it to do the relevant next step, such as calling the observe() of historgram we did above. **Notice : If the metric name does not exist, Null will be returned, so we must ensure that the metrics we want must have be successfully created.** Common APIs can be refered to the table above. For more details please refer to [rpc_var.h](/src/var/rpc_var.h). It's worth taking a look at the API on Counter: ~~~cpp class CounterVar : public RPCVar { GaugeVar *add(const std::map& labels); ~~~ This can add the Gauge value of a certain dimension into this Counter. The statistics of each dimension are separated. The labels are a map, that is, a dimension can be specified through multiple groups of labels, for example: ~~~cpp filter->counter("service_method_count")->add({"service", "Example"}, {"method", "Echo"}})->increase(); ~~~ Adn we can get the statistics calculated by `{service="Example",method="Echo"}`. #### (5) Reporting Reporting in SRPC filters is automatic, so users don't need to do anything. Next we will use a client to make some requests and check the format of data which will be reported. ~~~sh ./srpc_pb_client message: "Hi back" message: "Hi back" ~~~ Since Prometheus will use Pull mode, which means will pull through the PORT we set before and the URL path as /metrics. When pulling with the same way as Prometheus do, we can get these with a simple local request. ~~~sh curl localhost:8080/metrics # HELP total_request_count total request count # TYPE total_request_count gauge total_request_count 2.000000 # HELP total_request_method request method statistics # TYPE total_request_method counter total_request_method{method="Echo",service="Example"} 2.000000 # HELP total_request_latency request latency nano seconds # TYPE total_request_latency summary total_request_latency{quantile="0.500000"} 645078.500000 total_request_latency{quantile="0.900000"} 645078.500000 total_request_latency_sum 1290157.000000 total_request_latency_count 2 # HELP echo_request_size Echo request size # TYPE echo_request_size histogram echo_request_size_bucket{le="1.000000"}0 echo_request_size_bucket{le="10.000000"}0 echo_request_size_bucket{le="100.000000"}2 echo_request_size_bucket{le="+Inf"} 2 echo_request_size_sum 40.000000 echo_request_size_count 2 ~~~ It can be seen that we obtained four types of metrics by sending 2 requests with tutorial-02 client, among which the histogram was created by us and gauge, counter and summary were created by filter. The default metrics will be increased for the convinience of users. ### 3. Features of reporting to Prometheus The main features of reporting to Prometheus have just been described: 1. Use the Pull mode and will collected regularly; 2. PORT is required to be pull; 3. Return data content through /metrics; 4. The data content is in the string format defined by Prometheus; The interface reference is as follows: ![prometheus_img](https://raw.githubusercontent.com/wiki/holmes1412/holmes1412/workflow-prometheus_example.png) ### 4. Features of reporting OpenTelemetry The main features of reporting to OpenTelemetry: 1. Use the push mode and will send http requests regularly; 2. URL is required to make report; 3. Reports data through /v1/metrics; 4. The data content is [protobuf] (/src/module/proto/opentelemetry_metrics.proto) as agreed by OpenTelemetry; Basic APIs references: ~~~cpp class RPCMetricsOTel : public RPCMetricsFilter { public: RPCMetricsOTel(const std::string& url); RPCMetricsOTel(const std::string& url, unsigned int redirect_max, unsigned int retry_max, size_t report_threshold, size_t report_interval); ~~~ The time interval to make report and the numbers of requests to make a report can be specified. By default, the filter will accumulate 100 requests or report once every second. ### 5. Var Would it be the bottle neck of performance when every request use the globally unique name to get the metrics, especially multi-threaded operations with the complicated metrics (histogram or summary)? The answer is NEVER. Because the APIs for getting metrics through filters are **thread-local**. SRPC builds a thread-local var system. Every time we get a var, we get its thread-local var pointer. Therefore every time we calculate will only be collected by the current thread. That's why multiple threads will not be conflicted by global mutex. Moreover, since reporting is always asynchronous, a global API expose() will get all the vars in all the threads and reduce them together when reports. Lastly the data will be reported in the format with specific filter. sogou-srpc-ac65014/docs/en/installation.md000066400000000000000000000075641525217424400205370ustar00rootroot00000000000000# Installation ## 1. Install from source code on Linux SRPC depends on the following modules : **CMake**(Require >= v3.6.0), **OpenSSL**(Recommend >= v1.1.0), **Protobuf**(Require >= v3.5.0) Will get the following output: 1. static library: libsrpc.a (or dylib) 2. dynamic library: libsrpc.so (or dll) 3. tool for generating code: srpc_generator - **cmake** ~~~sh git clone --recursive https://github.com/sogou/srpc.git cd srpc make make install # compile tutorial cd tutorial make ~~~ - **bazel** ~~~sh git clone --recursive https://github.com/sogou/srpc.git cd srpc bazel build ... # It can compile out lib and src generator and all tutorials into bazel-bin/ ~~~ In addition, we can use srpc_tools to install and deploy skeleton project. Refer to the usage:[srpc/tools/README.md](srpc/tools/README.md) Workflow, snappy and lz4 can also be found via installed package in the system. If the submodule dependencies are not pulled in third\_party by '--recursive', they will be searched from the default installation path of the system. The version of snappy is required v1.1.6 or above. If you need to install **Protobuf** from source code, refer to the command: ~~~sh git clone -b 3.20.x https://github.com/protocolbuffers/protobuf.git protobuf.3.20 cd protobuf.3.20 sh autogen.sh ./configure make -j4 make install ~~~ ## 2. Debian Linux SRPC has been packaged for Debian. It is currently in Debian sid (unstable) but will eventually be placed into the stable repository. In order to access the unstable repository, you will need to edit your /etc/apt/sources.list file. sources.list has the format: `deb ` Simply add the 'unstable' sub branch to your repo: ~~~~sh deb http://deb.debian.org/ main contrib non-free --> deb http://deb.debian.org/ unstable main contrib non-free ~~~~ Once that is added, update your repo list and then you should be able to install it: ~~~~sh sudo apt-get update ~~~~ To install the srpc library for development purposes: ~~~~sh sudo apt-get install libsrpc-dev ~~~~ To install the srpc library for deployment: ~~~~sh sudo apt-get install libsrpc ~~~~ ## 3. Fedora Linux SRPC has been packaged for Fedora. To install the srpc library for development purposes: ~~~~sh sudo dnf install srpc-devel ~~~~ To install the srpc library for deployment: ~~~~sh sudo dnf install srpc ~~~~ ## 4. Windows There is no difference in the srpc code under the Windows version, but users need to use the [windows branch](https://github.com/sogou/workflow/tree/windows) of Workflow Moreover, srpc_tools is not supported on Windows. ## 5. MacOS - Install dependency `OpenSSL` ``` brew install openssl ``` - Install `CMake` ``` brew install cmake ``` - Specify the `OpenSSL` environment variable The soft link of OpenSSL will not be automatically built after installation by brew, because LibreSSL is provided by default under MacOS. We need to manually configure the execution path, compilation path, and find_package path into the environment variables of cmake. You can execute `brew info openssl` to view relevant information, or configure it as follows: ``` echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile echo 'export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"' >> ~/.bash_profile echo 'export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"' >> ~/.bash_profile echo 'export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"' >> ~/.bash_profile echo 'export OPENSSL_ROOT_DIR=/usr/local/opt/openssl' >> ~/.bash_profile echo 'export OPENSSL_LIBRARIES=/usr/local/opt/openssl/lib' >> ~/.bash_profile ``` If you use `zsh`, you need one more step to load the bash configuration: ``` echo 'test -f ~/.bash_profile && source ~/.bash_profile' >> ~/.zshrc source ~/.zshrc ``` The remaining steps are no different from compiling on Linux. sogou-srpc-ac65014/docs/en/rpc.md000066400000000000000000000614271525217424400166200ustar00rootroot00000000000000[中文版](/docs/rpc.md) ## Comparison of basic features | RPC | IDL | Communication | Network data | Compression | Attachment | Semi-synchronous | Asynchronous | Streaming | | --------------------------- | --------- | ------------- | ------------ | -------------------- | ------------- | ---------------- | ------------- | ------------- | | Thrift Binary Framed | Thrift | TCP | Binary | Not supported | Not supported | Supported | Not supported | Not supported | | Thrift Binary HttpTransport | Thrift | HTTP | Binary | Not supported | Not supported | Supported | Not supported | Not supported | | GRPC | PB | HTTP2 | Binary | gzip/zlib/lz4/snappy | Supported | Not supported | Supported | Supported | | BRPC Std | PB | TCP | Binary | gzip/zlib/lz4/snappy | Supported | Not supported | Supported | Supported | | SRPC Std | PB/Thrift | TCP | Binary/JSON | gzip/zlib/lz4/snappy | Supported | Supported | Supported | Not supported | | SRPC Std HTTP | PB/Thrift | HTTP | Binary/JSON | gzip/zlib/lz4/snappy | Supported | Supported | Supported | Not supported | ## Basic concepts - Communication layer: TCP/TPC\_SSL/HTTP/HTTPS/HTTP2 - Protocol layer: Thrift-binary/BRPC-std/SRPC-std/SRPC-http/tRPC-std/tRPC-http - Compression layer: no compression/gzip/zlib/lz4/snappy - Data layer: PB binary/Thrift binary/JSON string - IDL serialization layer: PB/Thrift serialization - RPC invocation layer: Service/Client IMPL ## RPC Global - Get srpc version `srpc::SRPCGlobal::get_instance()->get_srpc_version()` ## RPC Status Code | Name | Value | Description | | ----------------------------------- | ----- | -------------------------------------------------------- | | RPCStatusUndefined | 0 | Undefined | | RPCStatusOK | 1 | Correct/Success | | RPCStatusServiceNotFound | 2 | Cannot find the RPC service name | | RPCStatusMethodNotFound | 3 | Cannot find the RPC function name | | RPCStatusMetaError | 4 | Meta error/ parsing failed | | RPCStatusReqCompressSizeInvalid | 5 | Incorrect size for the request when compressing | | RPCStatusReqDecompressSizeInvalid | 6 | Incorrect size for the request when decompressing | | RPCStatusReqCompressNotSupported | 7 | The compression type for the request is not supported | | RPCStatusReqDecompressNotSupported | 8 | The decompression type for the request is not supported | | RPCStatusReqCompressError | 9 | Failed to compress the request | | RPCStatusReqDecompressError | 10 | Failed to decompress the request | | RPCStatusReqSerializeError | 11 | Failed to serialize the request by IDL | | RPCStatusReqDeserializeError | 12 | Failed to deserialize the request by IDL | | RPCStatusRespCompressSizeInvalid | 13 | Incorrect size for the response when compressing | | RPCStatusRespDecompressSizeInvalid | 14 | Incorrect size for the response when compressing | | RPCStatusRespCompressNotSupported | 15 | The compression type for the response is not supported | | RPCStatusRespDecompressNotSupported | 16 | The decompression type for the response not supported | | RPCStatusRespCompressError | 17 | Failed to compress the response | | RPCStatusRespDecompressError | 18 | Failed to decompress the response | | RPCStatusRespSerializeError | 19 | Failed to serialize the response by IDL | | RPCStatusRespDeserializeError | 20 | Failed to deserialize the response by IDL | | RPCStatusIDLSerializeNotSupported | 21 | IDL serialization type is not supported | | RPCStatusIDLDeserializeNotSupported | 22 | IDL deserialization type is not supported | | RPCStatusURIInvalid | 30 | Illegal URI | | RPCStatusUpstreamFailed | 31 | Upstream is failed | | RPCStatusSystemError | 100 | System error | | RPCStatusSSLError | 101 | SSL error | | RPCStatusDNSError | 102 | DNS error | | RPCStatusProcessTerminated | 103 | Program exited&terminated | ## RPC IDL - Interface Description Languaue file - Backward and forward compatibility - Protobuf/Thrift ### Sample You can follow the detailed example below: - Take pb as an example. First, define an `example.proto` file with the ServiceName as `Example`. - The name of the rpc interface is `Echo`, with the input parameter as `EchoRequest`, and the output parameter as `EchoResponse`. - `EchoRequest` consists of two strings: `message` and `name`. - `EchoResponse` consists of one string: `message`. ~~~proto syntax="proto2"; message EchoRequest { optional string message = 1; optional string name = 2; }; message EchoResponse { optional string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ ## RPC Service - It is the basic unit for SRPC services. - Each service must be generated by one type of IDLs. - Service is determined by IDL type, not by specific network communication protocol. ### Sample You can follow the detailed example below: - Use the same `example.proto` IDL above. - Run the official `protoc example.proto --cpp_out=./ --proto_path=./` to get two files: `example.pb.h` and `example.pb.cpp`. - Run the `srpc_generator protobuf ./example.proto ./` in SRPC to get `example.srpc.h`. - Derive `Example::Service` to implement the rpc business logic, which is an RPC Service. - Please note that this Service does not involve any concepts such as network, port, communication protocol, etc., and it is only responsible for completing the business logic that convert `EchoRequest` to `EchoResponse`. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; ~~~ ## RPC Server - Each server corresponds to one port - Each server corresponds to one specific network communication protocol - One service may be added into multiple Servers - One Server may have one or more Services, but the ServiceName must be unique within that Server - Services from different IDLs can be added into the same Server ### Sample You can follow the detailed example below: - Follow the above `ExampleServiceImpl` Service - First, create an RPC Server and determine the proto file. - Then, create any number of Service instances and any number of Services for different protocols, and add these services to the Server through the `add_service()`interface. - Finally, use `start()` or `serve()` to start the services in the Server and handle the upcoming rpc requests through the Server. - Imagine that we can also derive more Serivce from `Example::Service`, which have different implementations of rpc `Echo`. - Imagine that we can create N different RPC Servers on N different ports, serving on different network protocols. - Imagine that we can use `add_service()` to add the same ServiceIMPL instance on different Servers, or we can use `add_service()` to add different ServiceIMPL instances on the same server. - Imagine that we can use the same `ExampleServiceImpl`, serving BRPC-STD, SRPC-STD, SRPC-Http at three different ports at the same time. - And we can use `add_service()` to add one `ExampleServiceImpl` related to Protobuf IDL and one `AnotherThriftServiceImpl` related to Thrift IDL to the same SRPC-STD Server, and the two IDLs work perfectly on the same port! ~~~cpp int main() { SRPCServer server_srpc; SRPCHttpServer server_srpc_http; BRPCServer server_brpc; ThriftServer server_thrift; TRPCServer server_trpc; TRPCHttpServer server_trpc_http; ExampleServiceImpl impl_pb; AnotherThriftServiceImpl impl_thrift; server_srpc.add_service(&impl_pb); server_srpc.add_service(&impl_thrift); server_srpc_http.add_service(&impl_pb); server_srpc_http.add_service(&impl_thrift); server_brpc.add_service(&impl_pb); server_thrift.add_service(&impl_thrift); server_trpc.add_service(&impl_pb); server_trpc_http.add_service(&impl_pb); server_srpc.start(1412); server_srpc_http.start(8811); server_brpc.start(2020); server_thrift.start(9090); server_trpc.start(2022); server_trpc_http.start(8822); getchar(); server_trpc_http.stop(); server_trpc.stop(); server_thrift.stop(); server_brpc.stop(); server_srpc_http.stop(); server_srpc.stop(); return 0; } ~~~ ## RPC Client - Each Client corresponds to one specific target/one specific cluster - Each Client corresponds to one specific network communication protocol - Each Client corresponds to one specific IDL ### Sample You can follow the detailed example below: - Following the above example, the client is relatively simple and you can call the method directly. - Use `Example::XXXClient` to create a client instance of some RPC. The IP+port or URL of the target is required. - With the client instance, directly call the rpc function `Echo`. This is an asynchronous request, and the callback function will be invoked after the request is completed. - For the usage of the RPC Context, please check [RPC Context](/docs/en/rpc.md#rpc-context). ~~~cpp #include #include "example.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello!"); req.set_name("SRPCClient"); WFFacilities::WaitGroup wait_group(1); client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } ~~~ ## RPC Context - RPCContext is used specially to assist asynchronous interfaces, and can be used in both Service and Client. - Each asynchronous interface will provide a Context, which offers higher-level functions, such as obtaining the remote IP, the connection seqid, and so on. - Some functions on Context are unique to Server or Client. For example, you can set the compression mode of the response data on Server, and you can obtain the success or failure status of a request on Client. - On the Context, you can use ``get_series()`` to obtain the SeriesWork, which is seamlessly integrated with the asynchronous mode of Workflow. ### RPCContext API - Common #### `long long get_seqid() const;` One complete communication consists of request+response. The sequence id of the communication on the current socket connection can be obtained, and seqid=0 indicates the first communication. #### `std::string get_remote_ip() const;` Get the remote IP address. IPv4/IPv6 is supported. #### `int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;` Get the remote address. The in/out parameter is the lower-level data structure sockaddr. #### `const std::string& get_service_name() const;` Get RPC Service Name. #### `const std::string& get_method_name() const;` Get RPC Method Name. #### `SeriesWork *get_series() const;` Get the SeriesWork of the current ServerTask/ClientTask. #### `bool get_http_header(const std::string& name, std::string& value);` If using the HTTP protocol, get the value in the HTTP header according to the name ### RPCContext API - Only for client done #### `bool success() const;` For client only. The success or failure of the request. #### `int get_status_code() const;` For client only. The rpc status code of the request. #### `const char *get_errmsg() const;` For client only. The error info of the request. #### `int get_error() const;` For client only. The error code of the request. #### `void *get_user_data() const;` For client only. Get the user\_data of the ClientTask. If a user generates a task through the ``create_xxx_task()`` interface, the context can be recorded in the user_data field. You can set that field when creating the task, and retrieve it in the callback function. ### RPCContext API - Only for server process #### `void set_data_type(RPCDataType type);` For Server only. Set the data packaging type - RPCDataProtobuf - RPCDataThrift - RPCDataJson #### `void set_compress_type(RPCCompressType type);` For Server only. Set the data compression type (note: the compression type for the Client is set on Client or Task) - RPCCompressNone - RPCCompressSnappy - RPCCompressGzip - RPCCompressZlib - RPCCompressLz4 #### `void set_attachment_nocopy(const char *attachment, size_t len);` For Server only. Set the attachment. #### `bool get_attachment(const char **attachment, size_t *len) const;` For Server only. Get the attachment. #### `void set_reply_callback(std::function cb);` For Server only. Set reply callback, which is called after the operating system successfully writes the data into the socket buffer. #### `void set_send_timeout(int timeout);` For Server only. Set the maximum time for sending the message, in milliseconds. -1 indicates unlimited time. #### `void set_keep_alive(int timeout);` For Server only. Set the maximum connection keep-alive time, in milliseconds. -1 indicates unlimited time. #### `bool set_http_code(int code);` For Server only. If using the HTTP protocol, set the http status code. Only works if the srpc framework handles correctly. #### `bool set_http_header(const std::string& name, const std::string& value);` For Server only. If using the HTTP protocol, set into http header. If the name is set, old value will be overwritten. #### `bool add_http_header(const std::string& name, const std::string& value);` For Server only. If using the HTTP protocol, set into http header. Will keep multiple values if the name is set. #### `void log(const RPCLogVector& fields);` For Server only. For transparent data transmission, please refer to the log semantics in OpenTelemetry. #### `void baggage(const std::string& key, const std::string& value);` For Server only. For transparent data transmission, please refer to the baggage semantics in OpenTelemetry. #### `void set_json_add_whitespace(bool on);` For Server only. For JsonPrintOptions, whether to add white space and so on to make JSON output easy to read. #### `void set_json_always_print_enums_as_ints(bool flag);` For Server only. For JsonPrintOptions, whether to always print enums as ints. #### `void set_json_preserve_proto_field_names(bool flag);` For Server only. For JsonPrintOptions, whether to preserve proto field names. #### `void set_json_always_print_fields_with_no_presence(bool flag);` For Server only. For JsonPrintOptions, whether to always print fields with no presence. ## RPC Options ### Server Parameters | Name | Default value | Description | | ------------------------ | --------------------------- | ------------------------------------------------------------ | | max\_connections | 2000 | The maximum number of connections for the Server. The default value is 2000. | | peer\_response\_timeout: | 10\* 1000 | The maximum time for each read IO. The default value is 10 seconds. | | receive\_timeout: | -1 | The maximum read time of each complete message. -1 indicates unlimited time. | | keep\_alive\_timeout: | 60 \* 1000 | Maximum keep_alive time for idle connection. -1 indicates no disconnection. 0 indicates short connection. The default keep-alive time for long connection is 60 seconds | | request\_size\_limit | 2LL \* 1024 \* 1024 \* 1024 | Request packet size limit. Maximum: 2 GB | | ssl\_accept\_timeout: | 10 \* 1000 | SSL connection timeout. The default value is 10 seconds. | ### Client Parameters | Name | Default value | Description | | ------------ | ----------------------------------- | ------------------------------------------------------------ | | host | "" | Target host, which can be an IP address or a domain name | | port | 1412 | Destination port number. The default value is 1412 | | is\_ssl | false | SSL switch. The default value is off | | url | "" | The URL is valid only when the host is empty. URL will override three items: host/port/is\_ssl | | task\_params | The default configuration for tasks | See the configuration items below | ### Task Parameters | Name | Default value | Description | | -------------------- | ---------------- | ------------------------------------------------------------ | | send\_timeout | -1 | Maximum time for sending the message. The default value is unlimited time. | | receive\_timeout | -1 | Maximum time for sending a response. The default value is unlimited time. | | watch\_timeout | 0 | The maximum time of the first reply from remote. The default value is 0, indicating unlimited time. | | keep\_alive\_timeout | 30 \* 1000 | The maximum keep-alive time for idle connections. -1 indicates no disconnection. The default value is 30 seconds. | | retry\_max | 0 | Maximum number of retries. The default value is 0, indicating no retry. | | compress\_type | RPCCompressNone | Compression type. The default value is no compression. | | data\_type | RPCDataUndefined | The data type of network packets. The default value is consistent with the default value for RPC. For SRPC-Http protocol, it is JSON, and for others, they are the corresponding IDL types. | ## Integrating with the asynchronous Workflow framework ### 1. Server You can follow the detailed example below: - Echo RPC sends an HTTP request to the upstream modules when it receives the request. - After the request to the upstream modules is completed, the server populates the body of HTTP response into the message of the response and send a reply to the client. - We don't want to block/occupy the handler thread, so the request to the upstream must be asynchronous. - First, we can use `WFTaskFactory::create_http_task()` of the factory of Workflow to create an asynchronous http_task. - Then, we use `ctx->get_series()` of the RPCContext to get the SeriesWork of the current ServerTask. - Finally, we use the `push_back()` interface of the SeriesWork to append the http\_task to the SeriesWork. ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [request, response](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); response->mutable_message()->assign((const char *)data, len); } else response->set_message("Error: " + std::to_string(task->get_error())); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); }); ctx->get_series()->push_back(http_task); } }; ~~~ ### 2. Client You can follow the detailed example below: - We send two requests in parallel. One is an RPC request and the other is an HTTP request. - After both requests are finished, we initiate a calculation task again to calculate the sum of the squares of the two numbers. - First, use `create_Echo_task()` of the RPC Client to create an rpc\_task, which is an asynchronous RPC network request. - Then, use `WFTaskFactory::create_http_task` and `WFTaskFactory::create_go_task` in the the factory of Workflow to create an asynchronous network task http\_task and an asynchronous computing task calc\_task respectively. - Finally, use the serial-parallel graph to organize three asynchronous tasks, in which the multiplication sign indicates parallel tasks and the greater than sign indicates serial tasks and then execute ``start()``. ~~~cpp void calc(int x, int y) { int z = x * x + y * y; printf("calc result: %d\n", z); } int main() { Example::SRPCClient client("127.0.0.1", 1412); auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { std::string body; const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); body.assign((const char *)data, len); printf("%s\n\n", body.c_str()); } else printf("Http request fail\n\n"); }); auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4); EchoRequest req; req.set_message("Hello!"); req.set_name("1412"); rpc_task->serialize_input(&req); WFFacilities::WaitGroup wait_group(1); SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) { wait_group.done(); }); series->push_back(rpc_task); series->push_back(calc_task); series->start(); wait_group.wait(); return 0; } ~~~ ### 3. Upstream SRPC can directly use any component of Workflow, the most commonly used is [Upstream](https://github.com/sogou/workflow/blob/master/docs/en/about-upstream.md), any kind of client of SRPC can use Upstream. You may use the example below to construct a client that can use Upstream through parameters: ```cpp #include "workflow/UpstreamManager.h" int main() { // 1. create upstream and add server instances UpstreamManager::upstream_create_weighted_random("echo_server", true); UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412"); UpstreamManager::upstream_add_server("echo_server", "192.168.10.10"); UpstreamManager::upstream_add_server("echo_server", "internal.host.com"); // 2. create params and fill upstream name RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.host = "srpc::echo_server"; // this scheme only used when upstream URI parsing client_params.port = 1412; // this port only used when upstream URI parsing and will not affect the select of instances // 3. construct client by params, the rest of usage is similar as other tutorials Example::SRPCClient client(&client_params); ... ``` If we use the **ConsistentHash** or **Manual** upstream, we often need to distinguish different tasks for the selection algorithm. At this time, we may use the `int set_uri_fragment(const std::string& fragment);` interface on the client task to set request-level related information. This field is the fragment in the URI. For the semantics, please refer to [RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5), any infomation that needs to use the fragment (such as other information included in some other selection policy), you may use this field as well. sogou-srpc-ac65014/docs/images/000077500000000000000000000000001525217424400163435ustar00rootroot00000000000000sogou-srpc-ac65014/docs/images/benchmark1.png000066400000000000000000001163661525217424400211010ustar00rootroot00000000000000‰PNG  IHDR*Šv)ÈsRGB®ÎégAMA±� üa pHYsÃÃÇo¨dœ‹IDATx^íý ˜åy¯�›Åƒ ÆvÇvbo8!1v8^b±?ãÄ'^‚ç²uìÏûû;ù|t|£ �X…02»�Ò°Š]!v„�6 I !‰UhA!¡åùæ©éwôv©º§»§¥™îû¾®çÒLuUuõÝo¿ê÷7oU½ÇêAÔ ‚¨ P7 n4@Ý h€ºAÐuƒ êAÔ ‚¨ P7 n4@Ý hh~ñ‹_ØÀ�»«µµµðô7ÒïåÉ'Ÿ\x¤y7n\÷ë_´hQa)ô€ŽŽŽ¢�©ACÿ%4x5Á�ÿûú믖¾fòäÉÝmÒÈ‚  ˆƒ†¬€!¤ËÿZÞÖÖ–9˜óÇJmãq^¶lYaͽñc;v¬ 4¨{;ÿÙ—ùc=áÇãÇCؾÜó6þ^†×^ îÇߟôû×Óûvï½÷­Êû£F�²öööÂšÅøûé�lj{©õ{">¿Ëᳺéj¶öÒÜQúóêí%Ý7Äm’0JAÐÐô4ø`!<^ª|€‘²Ö‹Ë–YÓÚãi索~ñ€']Í2À‰u•âƒûxÀ_ª²B€JÚIzà_.Äòª$TJã¡EØÞC‡rÄm¿T¹�ZŽ£Qˆß£¬÷ÝÝ”k3Þ.ñ¾zzo y!hhª âAƒ‡ñ_½ÓƒÈ°Ü+àùLÏ2ˆ‰C¼Ä>~_^?ްÿ9 ¾½ÿNÐ�MzF‚»îü}‹Û�Wz Xª�¤Ã„ðžú>òx0ïÏéëxÛ¨v€ïÛÆûì‰Rm?ü…><–n§ÍD©÷5‡zþ^¼}¸Ó¬mÊAÐÐÔ48ñà4= ˽bâ¦W˜Õ�^^í 3ï# ”›‘j‚÷ÿU:0ÆÄm!ý~—k'ñ Ýg8ñú0Õ(ªÙg¹¶‡^Þ>›‘rïkìÏD€z@ÐÐô&hˆ·õŠ)µÜ‰ �B<›! Fk!kß=þ‚¶}à”5U<¬ãƒqZÂ_týøã¿Þg tã×[½ž?¦š ! §„˜ôà;>õ¥\;‰ m,kYo‰_s|l¥è©í‡Ç¼ÒmÉ�OÓðòßKµ9½á½ò÷ÐwŸa†�¿ÿ�¸¥gŽÄ�ùÏiü½Œg ùsù¾³‚’ôºþsxïÜ_XžUþxìÏŸ§§`¯Ô±Çí>]é×XÍë€þ A@ÐÓ`+†AH žÑ�Þ6,÷J?faæ•5À®”ô $ èJá¦r�xè„åñóxùë�g¾Ï4ñk £z>Œÿë ްnúùÒÄÏ·‡rí$~,gÜvÂòRƒôJpŸa_åÂ’˜rm?ªÄÄÁLV¥Ûoì7®Øeüü•ºL?Vêy¼¼�y[ ÄûI—{‰ÛrVùãiGî½Üg·Ô±—kÿñzÕ¼>è¿44µ ¾]<@Hÿå5,÷ЉŸÏ+„ñ2_§VÒû僔¬¿zÆeÏëëŃÀx–¥+¸‹Ã„x»xàæûÔûùþ{x¬'âuã÷8‹Rë–j'N¼M<ó"^ª§Áj)â¿–÷ôq[ñc ¤ýÇǾNhS¾¯ðyðñsøòð^ƾ¼âç/ç²Ôcqøám=|®b/ñL¡p¬î;~ ~¡:åŽÅ‰åûöå逯§}9~,±ÇplÕ¾>è¿44¥[�¬�DºÒÓ›�øñ€?W<€óC ^?èÔ‚oøãбñà%=ð‰½Ä�…e^>°ñ��z€(>=" ÆËƒ/ÅóâA|OÄë¦�#M©uýçôr?®x¹W|œþxì%.o'a0Y q»J»(Eì¸TŃZÇ�¥ÛiüZÃcñúéÏIüÚãÏ^–Ë@©Çâöž&«-„ß}»ržËKÀ_WâòeqHVɾ¼M‡ub_Õ¾>è¿44ñ`+ìâÁAV•0d­Wz ?–ÀÕŠäãAHzÿ=½¶P¥‘,â™ >8 Ä¥ðºÏ¨fð¯[êý ”Z·’ׇ<1 ø ;=XÍj�Y”š-ÒqÛÏ*ô¦áYí)«Âk�×O·ëRŸ½Øeúý(õXXÖS…&ý:¼�¥g%9åŽ%Æ=y0·s/O³Ú{Ö¾bé÷1,ï©* ™ ïBÐДì²¾M<(Lÿ¥Ö �¥ËŸ#kÀï¯Ô€´VÒÇfį­\ů/^^Šx°åƒ`ü„ßãéݪçwâ�dOÄë7¥ˆ_[ü>•z-¾¾b+ú€4žà•ègÏ Èj‹¥Èjû~œñìˆøýrbWå*ü%?Þ—?_L©Ï^Ög.Pê±°¬\ùg à^³^K:,w,¥ð÷ Þg0zÚWܶҮÂòr¿>è¿44¥;�Rƒƒx;ÿ‚Ÿ†Ç¼*!ž2ÿ¿^į#¼Îž>Y„õ½J|ýçxàUjp^ÏçwâAdOÄÇQnF@˜xÅáA-¯¥ñs¤œYÄÔJ�@©¶ïm9¦âcˆ½VrlåÖ/õüå\–z,,óªD¼�Ư7þü•;–rį;lWn_ñcYaWxÌ ‚€ Ô`'Pnpÿµ6ýXXîU >�· ®Y™8¨4Üë{•"�û 8ö2ªçwâ�^O¤„Rè8 J·•rí¤ZÂ~¼ÒVšø´‰ô샞(×öã°(~,vPÉÌ›rëdz7âç(ç2~_ãÇâ  š°%{ŒÃ¦Zß×ø8{šÑàÇŽ?+´tzûú ÿ@ÐÐô&hˆÃPÇ„å^•N|`Ì|pásžR!„/÷Çý8ãu| “5PñÃ2/ßäøc¾ß.&^¿q¸*=V>ì²â�upK<`öÇ}YL¹v’…ïÏËß›øyâÁw%áK¼~%ÿ˜rm?ýÞ„×·y÷_×ÀÛœO¼¯ôú¾½ñq{•ÚÆÛs©mbÏñcÞöÒíß÷¯ïû�ßc_'lK†…ö\øcþ\¾Ÿ°Ì÷o÷ ¥ÚHܶ|_YTûú ÿBÐÐô&hp|ð� a™W¥ø %k€ž®x�¿–R•ÈøïYëÅSjyšx°*k¥z~/+Y/àîãmÊ•=˜pß�žÚIšžžËÛA—#n•¬ÓSÛ�À>Ð ¤üéJï«T›v�áçJ·‰½Åž+ùìÄëg=*~_=@ˆÇëÄïyVùvñg5«�øãñ6é ^ª}}Ð!hhzlÅfÿ9MU>¼¾ZN›è 4ì A@ƒ�œ2è鿤ßKŸuЈ„»døëõ{$¾>�¿ h€:BÐuƒ êAÔ ‚¨ P7 n4@Ý h€ºAÐuƒ êAÔ ‚¨ P7* ÚÚÚlàÀ�ÖÑÑQXb6hРdYÞË ïRQÐàƒþÖÖÖî¿cÇŽM~öeþ¸£^}›ƒðû`?|à~vü÷eË–É—@ߦlÐàƒ}œtÐüÃcêåз)4Äþx°¯J-¯†5kÖØsÏ=GQEQEQEQM]>>Γ’AƒôÃÅãjooO‚€xà‚õòjp™ ¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿] Ò‰gøu².Ö¨^^ 4T-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû­)hpâYñlõòJ¡¡jÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛoÅACƒ†ª¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛo�AÃÀ�»kÙ²eɲ¶¶¶¢åþ{`РAÝË;:: Kë·¼Rh¨Zð«¿Zð«¿Zð«¿Zð«¿Zð«¿Zòö[6hð! òýç±cÇvÿ‡ �xßÎç^Ë«�†ª¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿Ÿ:áÿ.ø¿áçB0áøï> ¢^Ë«�†ª¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿Ÿ:ÑÚÚZXÒ4„åñ© é@À·ñÇêµ¼h¨Zð«¿Zð«¿Zð«¿Zð«¿Zð«¿Zòö[ñŒâ°!ÐÞÞžN_ (Š¢(Š¢(Š¢(Šê£Aƒã³²Ë= ˆ�Ôky5ä-²ÙÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛoÙ !žÁÏ\ˆñåa=¿ŽCÖEëµ¼h¨Zð«¿Zð«¿Zð«¿Zð«¿Zð«¿Zòö[6hð!¾CÀƒ…¬åN¼<ž…P¯å•BCÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·ßªN�èOÐPµàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛoà Ë_ÒÆÎüµ kÿ'3ã%¿Cý¡#Ђ_ ôù@ûÕ‚_-øÕ‚_-øÕ‚_-øÕ’·ß† |Ð0dÚ?ÚI·ý}w »ý¶ä•y…5 ^ÐhÁoý¡ÈÚ¯üjÁ¯üjÁ¯üjÁ¯–¼ý6TÐੌ{ÿdw?s­Í]y·=÷Ú"{mójÛ¶cka+¨:-øí™õo¿šÔªõK“ት³mþªûlöŠiÉçý®§¯²É�žŸÔ¥³wö'–ì¦-ž`3—Ýb‹ÖÌJöåûݱs{á™ Zh¿Zð«¿Zð«¿Zð«¿ZòöÛPAȉR5üö¶óîÿmÂçÙÍ‹.¶{—NN*>ÐX·å%Ûµ{gaÏ�†Ž@K#û}kû›É@þå7W&Ÿ5ÿüsçå�—= ðϦ‡cgþ;ëîŸÙwý$󳬪ÐGŒë–�‡dx¨á‡¿Øú-øÕ‚_-øÕ‚_-øÕ‚_-yûm¨ aÌôofNºíÛË*«‘ÓÿÑÆÞÿs›8ûÿØmóOµ™O]d‹^¸Åžy†­ßð¨Ù®w ÏÞ\Ðhé‹~=x ³< ðòøl|f€ȯ[x^xy@àÕÒþ½ÌÏX^õŸ˺ªöþ!ÔÈ;lcf J‚‘» {•Ÿ–±zÃs¶ù� ‹ÍýƒüjÁ¯üjÁ¯üjÁ¯–¼ý6Tаdæ'mØ”â°aðm'Ømwe3îþÓÎ?gnÿ¢�iÿ1åEëõ¦Î˜úßíâi_²«ïø¼M»÷K6kÆì‰�¶Õk›ý†Ù_ïªg~löìÏ»ê¹_š­<}O­¹Àì•¶=µþn³�vÕ¦³wVî© ¯xàÇ󨱶söf�Óõ;Ô ¼+.Vè]|Ðëûô°>³ÂiãçŒHÿK¾#¦ÿKf»ß—å3üØ|¦ƒëĹg&Çî3!üµ„ÙI^>cÂ_óìço·¡íÅ×hÜù{ûâß%³©>{Á_»Ïfˆ×­Gù1_ðà$Ç{Û—u§£ÏððÙ�_4¨ú(†ö«¿Zð«¿Zð«%o¿ 4Ø£Ÿ·%÷ÿ¾�nÿ[k™òw6jêñöÄ}h6«óefÔŽ÷·ubÏÝÿ!›ïÇìî»?e7Þñ6þö¿¶óÚ¿fC§œ�9X¨¶N¾íïì¬Îc¹ôöÿf×Ýñ—v×]Ÿ²‡ïýãäX×>p˜½õàA™ÇWq=ò³yGvÕüOí 6¼–œ¸'ÜðzaØžpcÕÙÅáÆº©{ ¯·—î 7¶­éZÖqhñsÏ9Üì�é…7zƒ².V8wÕ=É©<þ¸W@ÏxöÆdPíV ·Í• º/žur÷,‚x_}¡|ðîÇ5ú¾_&ÇNIðò�¾¿ôûë[üÒÃÉë §(lܺ®`ªv<`ñç>eêwmÔ=¿HN�(‡_§!ÌàðÙ>sÃgm„£îý… �VÝ)[=•ÏüpG¾ÿ«ç�¶©OŽOžwá‹$Çám¡¯_c†/ õÇß{.fš´_-øÕ‚_-øÕ‚_-yûí)6>à�sXñ@¸£ó÷—Æí0Çi_?h¯S<Ë s`¾õéŸÙËOþÄ–.8ÑæÎþ¦Ý5ó›6ù¾ìÒ;¿n£¦ý_I ñ«µ<ÔÝ~œ�»ýX›|ÇÑIè1÷Þ�'!ÈkjÛ< øuõ¥òðÁCŒç[ö„!´pçïö~€Ø §ôöb…õ˜ÚßÛ:yÊ?t‡døñz°áÇîa=|øë â0(v}m`\ïŽvë»[’‹Ìúk!�@î(?î0Ëm­åGlü}˜¾d’ÍZ>%igÏ¿±$ñ¾¯®3Ã…½ñ Ìß“84 3‹¼ÂéG^~ú�¿§ñ)Hé�!”/m ´ƒ8°óöàÏÚ„”‡ö«¿Zð«¿Zð«%o¿�£ÄÃßµÝd6ÿ“f¯ßRx@‡Owö)éþXÿr7ý©ñvݼ‘vñÌAvÖ]ÿ3óËa-5|êßÙy·Ã&ÜyœÝ|Ï—íÞ{>—ÌÄX~ÿ“™»fí—ô•ò™ Žêšiñô;[{áô‘pÚˆ›v…ûðÚýéb…•”ŸúàÇæ×ðcõS#üØÃµ¼ü }>ðñ×¼vÓŠÄA£^[`_ýGæmËÝ.}uaw{ò¶äíÈÛ�¢ýøûï§‚„÷ÝŸóáîHú+ï·ê1S$Mþ¢ÐÛ@ ô^õžíRïòk‹øq†S�¼¦û†ðº½Íº ïÝO£œæ“_tµàW ~µàW ~µäí·sôטôµ†ê_^ý/Ûþ×%ÿ¢êö¿vúùÚõ\Œ¼ã_l쌳‰sNéºxå“¿±EÏ�³çW\jëW]ºgöF�Û¿ úÁ0ݺǫã¿1Ý–Ïü¸��ö•äbšcÚ¿jËg|¬sàF× ÿå ]!€‡ x8à!ARœ&jóÌ÷Ùú™ï·Õ3Ofhøõ+|¶†Ÿ>â§‘xù)%^~ ¿Þ…_Gï}QÏ {ܱ֫ñ)¬õb…C§ý yo ¾4Âd¡ÏðpÉÛˆŽ}Ú¼·3os~ZE½ÐaPéý‡¥ÞG…Sff-›jƒÛ¿_´¾_C`þ‹÷5U �UáøÃ©.qŸàNwð ^BxèåA�_«%Ý? i?ÑZ>5Y'žyåý‹ï+\¿ÅŸËŸÓƒm?ÅÌ™ÞV˜yåAšk|ý–àÆC7}aæ•ÿŸåm«^³r|Ÿ\lS -øÕ‚_-øÕ’·ßÎQ^cÒˆ Õÿòã_ÖÃJÿâå_ÌÃ_9ëõ%< &üËžá÷k ø¼û;ƒÛ¿S´î°iß­üb…þewÖÿ±‹ø7;ëÎÎ/•wüSѾúB oÿvÖíßµÑwžh—Þÿ÷À/lò#-6yÞ»kqWHЗ.VµÑLÿ‘ùõ1ÂçÓCÌ0£ÊƒFÿŒûg½.³y¦d,Kjß_c¤Òr^½ ¼/ðRü¥~ÉŠ 6úöou]ìxÚ·ì‰ç./Kbá½MfM„k[øLŠª.¶9ç]³4ü´?ÍÄgoøÝ<^<·ë”¿NH¸%iµ·"õSA¸}¨þ#Û?5ÂûŽdú€Ìg>Ð Ïúìí‹ ƒÉ¾Hð¾ �Þõ'Ü�%œ=ÔrïaÆŠ‡[þÞ„€+¾;N½/”ZS•øÿ­õŽ%዇PèµàW ~µàWKÞ~;¿©4&4ÔÒø€"œÿ�\¼²óK¹Ï\_ü²¾l)«®+ܽ£k€¿åñ®/æ¯^k¶ö’®S:–ýÊì™w �6›ûñâ/íÊò`ŸӟÛ�Á�Å�É�Í�Ñ�ÕO=é8¤x;n*�þ¡vü3çý‡ýséŸOÿ¬ŸöƒÌÏ·_c¤ßÞ—¤+ô-é ׿‰Ë/tëŸót…ëáÄå·"×ʼnËûŽäô³×Èñ;-…þÄû¿ãO:÷áEukÁÿoò÷ÚßwÿÃESC; mÍÛN¸¯ÿ¿áÿø…-³Ú_=Ëg ú©<þü øg ^§m4ô¿Zð«¿Zð«%o¿�ßRjïð/Oé‹W¿=ûT‡~±BŸuà_Êý˹ê}€à³|–‚|ÖÂãÇu}Ù÷Ù Y_ö•å×·ƒŽ¥?í:¦pÇ/¿-kØøñ‡�ÏÖå]¯kۚ …ýCýñiýæœPÔ7øm{ŸXÚ9Ø„Áz\ûjà×¾ø\çUº^£¿v¿ÛOƒ„¥ˆ/ ìÿßøÿ;þÿ˜ÿ?äÿùÿKpùÿSñµ@Z¦~·¨ígìù)$>ëǯÁãÏáI#ß•£·ÐÿjÁ¯üjÁ¯–¼ýv~ iLh¨õÇÿz3,uˆ¦½X¡Þßzªk@ôÚ f/]Þõå}ù¯»ÂÿRï§>øü‡Ìì‹ ƒ/¿Pg|5Y€Ñ´ýC˜ñSj�ï³kÂ{.î*\ä5”_ì5´âf”\Äutûßv]C`êñöÄ}˜Ý©¾YMJÄdýÿÖ2õ{vÅÃ#“YvµÌ˜ðÙzbø)H>{Ѓ?½±iñ>†SåðýW ~µàWKÞ~;¿Y4&4T þeŒ‹ÖÀÎÎ/—þý͹fëïîÈù@~eëž¿¾ú—ü¬/ÿý¡úK€Ñ—¾è†™4¡|\l¸�#¯ô_òÝgð‡`+T¸Bõ¥ «?Ô#)öçµà¨bÇ¡ü4¨ð>„ ƒóty¿§^R†÷<.¿eoÜ6¼^½¦ë4‰øXgjöˆ®}­:»+ íÁ�ùá¯_ÏjÀP¢§ÿß<$ð™~xxà!B-w„òÐÂà Ÿ)èÝôS‘ü¢­ �·ë>z�‘Fƒï¿Zð«¿Zê U ~ø®ô@¿̿4®ø/Ï>8 •0ˆ §yxÅSÆ}°Kñ~ûKÕ3ÀxýæÒ_t=Ü:_?lë僦°_/Ð…ç AQ–{¯ùŸÚsüy^¤/ÕCìqª/ Ü·¿Røàõ#¼­.ø¬í~è Î¶õÉ®Û W‚¿ÞÍ ÷„œ„e©öÿ7?MÂOÑðÓ&üô ?�¢–;røµ&üÚ~ê¡_LÓ¯UÑg.Vúȸôö>{Ip^øl† 4AÓ× 5爮m½-{}—k^ô¾ŸiÁ¯üjÉÛog/ߘÐPµàWD­‰jð/óa°Õ¬F3Vx¿¼Ò}ü…÷Ù+¼ÿ^án*¡âÇ+´¥—'v:”ñsv®iÃMŽ´ÿõ÷²ÉC‰zùõ §ú, ¿Î‘_Â/$YËm¨}–…_DÕ¯-á³,üî0%¯qÔÛ@ÀÝÐOø{‘õ©ËŸ×/žì×GòÓ "ª‚ïgZð«¿ZòöÛÙ£7&4T-øÕÒ/ý`TV~qÏð½â™^Á‹Wú¯ù~—’àÒ+8özsîÿ^~ºNÞä”Aßé¼�5R(៣N­ò U.^u»ÍXr¹]ýÈ©6æÞŸYËÔïd† ¥Êoé|Þíß°¶»¾f÷Þó9[|ߨk¤fl5jD”…ïgZð«¿ZòöÛÙc7&4T-øÕ‚߈z¥nè×0ƒþ°¾×âoúŸoÙó<^á¹½ÒSöýB¡á˜½š Ú¯–~é×?}9”X{qç:·öcõcÏa†ÀË H. z÷ÝŸ²¶éÇØyí_³“oû»Ì ¡TµLù–�iÿ»zú_ÙÌ{ŽL.к~æû3Ÿ¯â ÇïïQx]Þ¿†¾ÑûÝt¿÷‰Þo'3žRAÈìNßÏùûÐÚåÌ/*[�SÌš<ˆ ÿÕ‚_-øÕ’·ßι1¡¡jÁ¯ü ðÁÁ^×À`j¿Ú¯–†÷»¯C‰>T»fígk8ÌÝ÷G6ý®ÏØÄé_°ÑíÇe† åjèÔÿacïþ�Ý8ëÿ¶YóÿÍ–.m›_¼|ï@ „£ŠSS*�ñäwÇñ`Ç/ŠëAADUÐÿjÁ¯üjÉÛog�Û˜ÐPµàW ~E0µ?h¿ZðÑC‰:ÌضåY[ýê,›¿òÎäâ‘~I¿˜dVÈP®ü¢•~ñÊp N¿¨¥_ÜRI¯Û/ADYè´àW ~µäí·³GmLh¨Zð«¿Zð«¿Zð[#åB‰R§5ø©U½9e@9C ƒp N¿m¦ß>Óo£YË-8}›qÃ’}ø¾|Ÿ¾ïz o¿MDÐ?hÁ¯üjÉÛog�Ù˜ÐPµàW ~µàW ~µàW@ƒŸZnÁé³|öÂ¥³×t Αwþ¸ûœóWÝg«7çȤ¦�ûú^uoÛ‰öà�?ß«½÷ô½jñì ì¹m{•ÓõÆK�Ûæõ+÷ªþŒ¿.ÂÜå¤aï/j»mÃ%(À-øÕ‚ßÒ¼¶yµ-~éa›ñì�I€àAÂÉSþ¡(@Ȭ)Ë:ëÔé?LNçðÀÁ÷½k÷ÎÂ3õcöE±ò ³Ù©Û˜úíY}–Ôú-øÕ’·ßΞ¨q¸jð{‹¡Æg,£êWýÁo[ëºÃ�¸úR0âëÔ2Þ¾ucæ~³Ê�!}\¥jåSS3_[V¹‡,?YõHûI™~³Êß‹¬÷(«<˜Éz�C]9$»˜0ä@›zñWº÷sÇøŠŽÁ�7>þ§¹¼ûu/{ôÚ"gé÷xçŽ|.××à‹‚üjÁoux8ðò›+퉵³íîg®MN¡}ß/3ƒ…Jꌻ~’\ˆòÆÇ.H ߯_ ¢^£Ü稃ˆ¸æªð¤P/è´àWKÞ~;{¡ÆÁ¯É�5� ¨þP¥fŒ0#G[yeX|x0%3®ýqw¸ñÐÍ¿, 7Ò�Öê¥ww‡¯¼ÐQnxð´¯ðãaFŽüæ_tëƒ_£Á¯Õà×lyÇ�2C…“nûvƲÒåwÃðëH\=´ÝõôU6wåÝöüKlãÖ¾ÛÉŠ©wáÛ­¹ ëî'Ðkè´àW AC/ðk1L\|�¿fó·�l»6¾”Ô»/,ÜSÏζm‹¦u×;s®¶­3Çw×[SNï®-7¶Í“þµ»6]ò/¶qìwºkýÈ/4|-9õ“6©eÿ½ü>:ü�mõˆ¿(ªU#>gÏŽøô^õÄ©GÚ¢áŸ(ª�Ûw ûÈ^5cè‡íž¡,ª»†aSØ«&>Ø®|PQ]Ý’ýìþVÌÈ¡zSמñ‘îpã†s?Un¤gïÌ¿sXw¸ñØýg…>ÃŶ¡6¾¶´;ÜxkÓšd§¦èÀo~ðE·þø©ÃR™Üþýäú~ ¿“…Ï‚¨è4Œå×�ðS9|_~[Ά;%#�D<|DvÈ�.¿vƒßÖõ­§ ;ƒj¡Ђ_- ½ÄƆ °+[ö³ë‡jÏÝ1²ðH~ìÞþvw°áµcíÓEGQ¸1ïÆ¢pãí;~SpÄáÆ›ãRnl8ûk™�€²–œúg�ƒú÷Ù•§túmyŸ=yêŸf®×—ë¥Ö£ûd02¡Ói<ˆÈªô6å*ëyKUú¸ËUÖk/Uiwå*Ë©J¿å*¼ïÞv÷Êö·ÃöeñsøúññÍö±¢×;¹sÈŠ|Þ˜zãçlÔbF޶Jù�8ô}Ih”‹–̹$ ˆžò–$¤ˆg¾@yø¢«Áý&œ2õ»6êž_$§Dd᳞{mQ2[ÁïbáׂðY µÜŽ3®†>%#ëö¬³4{轩eQÍÿTWH±©£°¨ú-øÕBÐP'𱡯áÆÎ×–…Û—Ü_p…÷\Pn\÷ïEG÷¬�3¾T4`ï®ÖŒeTÕ•=>Àž¾'̉ƒ¦ž*~{ªøýï©â¶ÓSÅm®§ŠÛkO·õž*à3˜ž>ýÏ‹‚²Å§eÛæßܽnÑóˆf<½Öúù¢ dňÏq¸±`øÇ‹Â�C¿(àˆÃ�›Rnxˆ·¥}UÌÈÑVoü^sêávýÈÙ­g¦�þŒÝyþlƸoÚÌË¿eó¯þqROßòïöÌ�§Û ^œÜÙéµGo´ OÜno½0·èsª?ãŸ{ô×�:Þ6�ûQò;ÔŸÞ|?{kû›I8à§cÜ»tr\<ëä$DÈ *­†8%#ëö¬;·˜½z­ÙÓ?캈dVààå§dø…%}–„Ϙ€’0Ö‚_- u‚†Z|àµaÔqEƒ&ÿÝC è=™áÓ�ÂoqÇ›.:1 Í6þö{ûÄí¾šñ‡/¤fðøÌ¤8àˆÃ�‡ýAQ¸qû�ß+ 8B°1¡¥ç9Tã•ÏÄ m ´ ŸÉåmÅÛŽ·¡y­f�Ÿýy{âìcì¹s¿’ÔÚóþ/{eü�mãÄÿ§¨o™|rQB½óÈõEŸ—Pñç)ÔÎWž-úÜ… ø:Îú›¢Ï̆Q›ôP_TßÏüš~QJŸ91kù”¦=%£¤ß]›jöìÏÍþpvààå�ù:¾®oE0¾Ð‚_-yûíìQª†¾0Pkdð›ÍØ?ă+ÅŒ§%­ŸéqFU{eÍxº²ó÷yÃ>–„EKG|ª;(š3ì�ŠfÀL/„C· >4 ®éƒ×¯¹ªåÀäØü´#?Ö©ƒë·Bèå§­ùësþš}6�gk[�ÎtV]}1Ù§Ï òc¹iðû“ß7œýÕ¤Ýûç`ë¬ Éçcû33“Ï�–v¿½ï.¾Ú_ÙWýo³œ’Q‘_Ÿµà³|C¹ Lú,Ÿ á³"|v0¾ƒ_-yûíìEªüjÁ¯üÖfäh)åwÛ£SŠB¤$HZ·²(H µ}ñ=E�’׆‡ÆÛëwœck¦ ·n9Ùž›ü+{êêÿŸ-ÿ�6÷¢ÿn�\x¼Ýÿ_Ç$5íÌ#­ý´?²›N=®~˜MÒ÷®?fWx¨â�…_?Åà ]<¬ð&2$xù5z~¤M<¥8È™Ôr@jdŵñ¼lÓE?èš•qã`{«ý¬¢Yîßg/%áÄŽí…wµ9é‹ýo#�’Q“_¿Nƒ_¯aþ§²¯Ù›=yB×(ßm »|T ß´àWKÞ~;{ŽÆ„†ª¿Z𫿘‘£¥¯úݹã�ä"“o®[Þ}W’e�^›\ŒòÉÇ$§|¤ý¤äb•÷·ýÀn¿ø«6íÂ/Ùä³ÿ8© ©»Eí‹*u±Í¶–’ ÁúéE>ƒâåÖ¿Ì ª­ g~9™ ä§=°X<û‚$¬ð[€zX1sòO“°ÂëÊ¡ïÛ+P¨µ& {¿Ý|ö'’»x̺øë¶ðÒ¿·§/ÿ¾­ºìD{í‚ï$§Yd u¯Ó�MfMlºìÇI8‘Ìš¸ã7]³&Ü’œJ“Ìšxm¹ízóÕ‚A=ÍÔ~ó:%cÚâ 6¸ý{Eë »ýÉ ‰ºá§Køi~ú„ŸF‘8xùé |ÛLú_-øÕ’·ßΡ1¡¡jÁ¯üjÁ¯üjÁoýyñééÖ6â°¢À`âðClö­¿²…w·&¡Ä´K�³ëÎúxÑ:µÔ¤SØ-c޶»¯ø{ë˜üs[tÛØsw�nkïkï» û¬›¯ùßöæ?˼‹Œª6œs|ò|ÉÅ^¯û÷®p⾋í�Ù“ºfM<;Ûv¬z¼kÖÄ;› ö*ÃC nº‡ºž’1%cYg�~×ÿÔ\¢‰o›Iÿ«¿ZòöÛÙ 4&4T-øÕ‚_-øÕ‚_-øÕàaÃMç}Ö& 9ÈnýÉd&D>k¯G±vÙýÉi² bâwmΔ_'×¹ðcy}Õ|{û¥§’[uú }ûwvß÷­Ûϱ-7K‚M—ü‹móíÌ AQɬ‰ÎçLNéè<?–䔎Îcócôcõ‹–n8ó«EÛqûÐòÔó” ŸAá³<Ððu½-g“Ý6“þW ~µäí·ó“ߘÐPµàW ~µàW ~µàWKoýî³ bõÂä:1>ëÀgø,Ôû¬Ÿ�à³’Y×ý»½yå/ºfMœs|QPßúbƲ/؆ÑÇ'טð‹_ú]9¸]he¤OÉ1ý‡™ÃI·};cÙßÛÐi'Ú¥³'†Ï¨¨Û­8›à¶™ô¿Zð«%o¿�ŸöÆ„†ª¿Zð«¿Zð«¿ZÔ~ûR‘…_¯Á¯Û�Ìšð[¢.¸%™¡à×wØr눮Y—ý8 'ü:YBoÊOIfH\÷ïÉsDôŒ‡æ�X&œ2õ»ö›ûÿ5¹»E¼¼Tù)~ ŸAáw¾¨ xÛLú_-øÕ’·ßÎOxcBCÕ‚_-øÕ‚_-øÕ‚_-ûÚo_"Òø.’Y«'w¾HfM̹ڶθ,™5áwÈ(y�‰ÖŒe=AD66xXàè{~‘Ü¥ÂñÓ$|öƒŸ6á§_Tz!J?Eïáw¼ð;XlÛ±5Ù_Í4Èm3éµàWKÞ~;?Ñ� U ~µàW ~µàW ~µôu¿ý-ˆpü´� £Ž+ Îúj×õf\–Ì”HNá¨Ãµ%š=ˆ¨¤ý®ûÕä´‹©OŽ·13UtñI_ǯáÛø¶½ºÈd?¾m&ý¯üjÉÛo秸1¡¡jÁ¯üjÁ¯üjÁ¯–þî·¯/>x‘Ýrê쪖í¦a‡Û‹3Î/<’b×NÛ¹~µ½»b^rI‚ˆê¨¥ýúlŸµà×{ðY •^p².™ìg·Í¤ÿÕ‚_-yûíüä–gàÀ�ݵlÙ²ÂR³Aƒu/ïèØó¡W/¯ªüjÁ¯üjÁ¯üjit¿û"ˆxô¾3lâ°÷­×6âðäNUCQ–zµ_¿õ¦_¯aò£ç'§dd éêõE&ûÁm3éµàWKÞ~;?­¥ikkëäûÏcÇŽÝëgÜCG½¼h¨Zð«¿Zð«¿Zð«¥Ùý*‚ˆ+2–yMv°M÷õ¤ü9¼ñçöÐÍ¿´Gï==))ž[Ж”‡/­x0)?¾ÍëWÚ[›ÖŽ:¢ÉƒUûõÐ`ñK'!‚‡ ò‹LöÑÛfÒÿjÁ¯–¼ýv~B+Ãþ8>ð÷ à¿ûlõòj ¡jÁ¯üjÁ¯üjÁ¯ü–§žAÄøŒeµ–Ϥ˜|ΑIM¹ðØÒÆ=§ÙãwްgîiOßük[>ùÙŠI?µåÿ[sîñ¶zÄ_ØÚÖ£3C†Jj_yµß\/2Ù‡n›Iÿ ¿ZòöÛù©,O8}¡µµµ°dï�¿?æÁ€zy5ÐPµàW ~µàW ~µàW ~{GVqÕÈ#2Ã�þP“†¾Ï®ñ{vÝðv˰߳)ƒ$uÏÐ&ußÐYǰ�$5wØÇlÑðO$µäÔ?³gG|:©Õ#þ¼;ÀP>ÓÖI#ް[Ç“üž7¹]drÞ6“þA ~µäí·ó“X>›ÁüAàW ~µàW ~µàW ~ë�ŸöÐ6â°¢üÄSØ’‡ÇÙ/=Þ}JÄŠÇoè>U"œ>1ÿÎaÉŒ¯{ÛNìž©pÓyGuÏ`ˆ÷Û_jbËþvÝàƒ’ºyð!{Œá¿o÷ž~¤Íý6÷ÒlÞ•'Ú¢ÿÍ–Þ{Ž=;ç²ÌSHžòf›”¾ÆðCk»FÉå"“9ß6“þA ~µäí·ó“W9>³Áñ ø‡`@½¼\$EQEQµïë‘ûÆÛµgÿ©Mr�]sæŸØœ{.Î\¯7µô™Åöä£3“zì‘v›?ëÚ¤:îú­=tû¹IÝ;ù×IÝ}í¿Ù´+þ)©Û.9Án:ÿKI]{öŸÙÕg|,©xàÞ—«Ô50®nýPâ$ËÕ¾ª¹‹gYû¼«lüÌÓìŒ;~–4¤kðÔïÛÝó¿íêÙçÛ½�Þf‹Ÿyýó¶`øÛ]CްI-d† q]úجk~”œòöæW Ïз¨ÛE&wm«ì¶™³ßoöÐ�ÅËænöƾ½Øf#Bÿ %o¿�Ÿ”Æ„†ª¿Zð«¿Zð«¿Zð«¿ ÂgC¼z­Ùξ{G�<¡Ð’·ßÎVÞ˜ÐPµàW ~µàW ~µàW ~µàWK_ñëw¤Xûì=¶à¦µ©ç|Ê&ôp+Í䎃Xǰ�Øsç|É6ÝÔ’Ü’ÓoÍé³'ö56ø)N™ú]uÏ/læ²[lâÜ3‹Â†½‡€_§¡§ÛfÎ>ØìÉÌ^ž`öîºÂ†Íýƒ–¼ýv¶ìÆ„†ª¿Zð«¿Zð«¿Zð«¿Zúªßw·m±ÕKﶇ'ÿÜn9ûO3Ƹ&¶ìoÓ†nû¨­8ýó¶yÒ¿ÚÛ÷]lï>;{Ÿ^d2íwí¦•Žß‘ÂïLQ¸mfÉjÒÛfÒ?hÉÛogKnLh¨Zð«¿Zð«¿Zð«¿Z𫥿ø}ç­u¶âñ¬ã¦ÿ¿M>óc™aC\“Z°;‡|À ÿc[5âs¶é¢Ø[ígÙ¶G§äz‘ÉR~«fÏ�8* B•ºÈ¤_÷ÁOÅX7êxÛ4îGÉïµP—À!Ѐ·Í¤Ð’·ßÎؘÐPµàW ~µàW ~µàW ~µàW ~ÍÞÞüJrG‹‡nþ¥M>çḚ̀!®«Z´»†‘ÜÑbõˆ?/ ü"“›®ø™m8ýKÅËGým2 ¢Vê88 rÛLÚ¯–¼ýv¶¸Æ„†ª¿Zð«¿Zð«¿Zð«¿Zð»7~G‹¥ó&ØŒklמñ‘̰!®ëÔ}G‹—Z�¶õ#¿hÏŽøtrÝ%nüþä÷M~¿ð µS÷ÀÁéǷͤýjÉÛog+kLh¨Zð«¿Zð«¿Zð«¿Zð«¿=óÆK�'w´¸{âwmÒ©2Ƹ®|�M8e¿¢e“Z°%§þ™m™|²mâÎ^ßNS88ýì¶™´_-yûílY� U ~µàW ~µàW ~µàW ~µà·:Â-»ÿl›>îë™w´¸"õ{¨ë¿oÏ©gͶÜ:¶?3ÓvïØ^Ø{õȧÜ6“ö«%o¿�­©1¡¡jÁ¯üjÁ¯üjÁ¯üjÁ¯üö¿£ÅÚe÷Ûü;‡%w´È B�﬩ƒ³G‡ÿ‰½Üú—™¡C-·Ðt¤�ƒÓGo›IûÕ’·ßÎÔ˜ÐPµàW ~µàW ~µàW ~µàW ~ëËö­+º ¤ŸZ‘:œû�ä.ﮘWSè œ>tÛLÚ¯–¼ýv¶šÆ„†ª¿Zð«¿Zð«¿Zð«¿Zð[^|zºµ�8¬(X¸bð�vEËEËBÅ¡ÃÚäb’©Ð¡†[cæ8öám3i¿ZòöÛÙRªüjÁ¯üjÁ¯üjÁ¯üjÁ¯n:ï³6aÈAvÃèOÚóOÞbï¼µ.¹›ÅãO° ƒÌ ¼n<Àæ ÿXQè°qÌ·íí»ÆØŽU�ž¡2r œœo›IûÕ’·ßΖјÐPµàW ~µàW ~µàW ~µàW ~µ”ò[�Ðaç+ÏöÖ3¹N·Í¤ýjÉÛogkhLh¨Zð«¿Zð«¿Zð«¿Zð«¿Z*ñB¿}fÖ],Be…›.ø¾m�qYÅ¡Ã> Ñm3i¿ZòöÛÙªüjÁ¯üjÁ¯üjÁ¯üjÁ¯–jý¾»m‹-{ôZ»·íÄC‡9ÃþÈV�øóâÐaæxÛ¹~uao¥Ùg�ƒSÇÛfÒ~µäí·ó]oLh¨Zð«¿Zð«¿Zð«¿Zð«¿Zzã·ÒÐá¦Áïß;t¸ìǶuÖ„C‡}88µÞ6sãƒf�k;gÑuê…ÿu'ïþ¡ó�nLèhµàW ~µàW ~µàW ~µàW ~µÔËooC‡w¹Þv½ùjao{³Ï§ÒÛfÎ~¿ÙC/›s¸ÙÓ ;‚z‘wÿÐùN6&t´Zð«¿Zð«¿Zð«¿Zð«¿Z~=tð»XÌœüS›tê€ÌÀÁ++txóÊ_” úDà¨ä¶™q-ølaC¨y÷�ïbcBG«¿Zð«¿Zð«¿Zð«¿Zð«EíwçŽwlåSS{ ®ü>›=ì#öüˆ£ºC‡Í“þÕ¶=:Åv¿½±°·=ô©ÀÁ©ä¶™>ËêJÞýCç»Ø˜ÐÑjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÓoÍ¡ÃéÇÚæ«~•:ô¹ÀÁYø¹ì Áïf±ý•ÂJPòî:ßÅÆ„ŽV ~µàW ~µàW ~µàW ~µàW˾ò‡m­È ¼²B‡-“O¶íOÜi»·¿]Ø[ üZ sËeöÎÊŠÐ[òn¿�ï`cBG«¿Zð«¿Zð«¿Zð«¿Zð«¥/øÝµk‡­]v¿=tó/íêÓ>œ8x…ÐṟNB‡ g~y¯Ð¡Ï6,ø¬í~è ÂÝ*Ø6<ò‘®S- ×äÝ~  &ð«¿Zð«¿Zð«¿Zð«¿ZúšßJC‡kZÞk3‡þÁžÐáì¯Ù–[GØögfÚîÛûLàÐí÷õ[ºN�aCÇnyYòn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-}Ùo=B‡µ–íÓÀ¡È¯ß¥Â†6øÅ!=€€šÉ»ý4@MàW ~µàW ~µàW ~µàW ~µô¿!t˜3å×vÝYÏ ¼ö Îý†½Õ~–­Z2Õ&>rFî�Ã^~ß^ÚuêD¼Ö\Pxª%ïöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒ_ý¾òB‡=Ò~R�¡Ã}C?dKG|ÊÖ�<& –·µ ügn�C¦ßmkº. ‡ Ï·„jÈ»ý4@MàW ~µàW ~µàW ~µàW ~µ4‚ßJB‡«Z, –^ü÷vEûÿ-Jú}w�ÙãLJ Kj¶{Ga¨„¼Û/AÔ~µàW ~µàW ~µàW ~µàWK£ù­6tX2úXí·e�CY¿;·˜=ýÃâ°añ·»–CEäÝ~  &ð«¿Zð«¿Zð«¿Zð«¿ZÙïë«Ú»[í†s?•8x…Ðáá3?k—O<¾î�C�~}ò_‡ �k¶ý• P޼Û/AÔ~µàW ~µàW ~µàW ~µàWK³ø}ã¥Ç{ &¶ìoSOý��¿ðhûÏ[¿U—À¡b¿+O/ü~áH(KÞí— j¿Zð«¿Zð«¿Zð«¿Zð«¥ýV:\Ù²¿�?ûmä„/…ÕUù}éò®[^†°ÁïN±yaáAÈ"ïöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒì~Cèp˘£3‡¤`ÿõ›�…'ßö?ìú¹g÷8Tí÷õ[Ì:ì üç ÷„4y·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üîáÍuËí±û϶[Ç“8tÖåCºB‡ÓÇÿ¥�|ó vò­ß¶kïd¯½òda/ÅÔäwS‡ÙÃÞ6ø,‡W¯-<1y·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üfSIè0¾e;ÿœ?HB‡–›N°«oø'{in›í~{£½ûÂB{süOlݨãmÓ¸%¿W…_ŸaîÇ÷„ ^«Ç„@Þí— j¿Zð«¿Zð«¿Zð«¿Zð«¿=SMèpÆåim¿;Îî>÷/lÜÈÃìò¡ïµq­lÖ¹ÇØ»ÏÎ.ì±BüÎ~QÈ8lXqRáApòn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-ø­ž|pŒM»ô¸ÌÀÁk|Ë~ö;¯xÙ�íþß~©°—*عÅìñãŠÃ†§Øu[LȽý4@MàW ~µàW ~µàW ~µàW ~µà·vÞÚ´ÆϾ`¯ÐáŠèç¸Æ�8Ôl×ÎÂÖU°ë�®p!žøzWÑääÝ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð[BèpË…Çf† ^ãOÙÏ6]ô{wżÂVUà3ü´‰8lxô˜®Ó+š˜¼Ûo�AÃÀ�»kÙ²eɲ¶¶¶¢åþ{`РAÝË;:: Kë·¼Rè´àW ~µàW ~µàW ~µàW ~µà·þ\~Úïe ^“N;Â^lý Û2ùdÛµ±ü­13yñÜâ°aÞ‘]ŽlRòn¿eƒ ßnmmíþ9¾lìØ±ÉϾ�‡N½–W�üjÁ¯üjÁ¯üjÁ¯üjÁ¯üÖŸŽ9çÛø¡ ã㟇`œö {ãÌ/ÙÖ™ãm÷Ží…-+ä•Î1«ßò2„ �|¤ë–˜MHÞí·lÐãþž‚|½€ÿî³ êµ¼è´àW ~µàW ~µàW ~µàW ~µàWƒ‡ ãNû=?x7b€]6úcEÁƒWÛ©‡ÙªŸ³�c¿cÛŸ™YزBÞ˜Þù$ö„ þ³/k2òn¿ >à „ þo8­!>µ!x0á�Õky5ÐhÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–Øï}ža—�8¤(lðÛbÎñQ[7òÛ|Õ¯l纕…µ+Àg1øl†6ø,ŸíÐDäÝ~+ Âà?‹öööîÇê(”Z^ .’¢(Š¢(Š¢(Šê5ÿñ™vÑ…G… ^W ;Ô–�øŒ½qÚ_ÛšϰeO-ÊÜ>]+Ÿ¾ÛÞ�ó‰=aCg­{ô?2×mÔÊ“ƒðû¬…r„Ç= ˆ�Ôky5ä-²ÙÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–,¿»vï´;gŸm—´HûÙ}§þ¡­ùyÛxÞ ¶ý‰; [ô€ßyÂï@… ¶ü×]wªhpòn¿eƒ†2”äûŒŸqàøéYq¬×òj #Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-åü®~ãi»xÜíw-ûmC¶gG|ÚÖ�ü‚½yå/lÇÚ§ [”aç³'¾^6<ýC³]ïVhLòn¿eƒÂuByè�^/�Šz-¯:-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒ“ß;·Ûm³N³‹N;¬(lðºw؇íÕÖ¿²õ§koÝ~Ží~gsa«ø â°áñãºBˆ%ïö[6hèÏÐhÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–Jý.{u‘�ÿ×ÉÅ!ã°¡mðA¶äÔ?Kf7l8çxÛ¶à³]; [•`ÅIÅa£ºN¯h@òn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕR�ß­ïn±ëf± Îü`QØàuÇÐØË­Ùu:ÅøŸØŽU�¶*Áš ŠÃ†¹7{{iáÁÆ!ïöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-µø}bílûÍø/ظ¡… “Z´EÃ?‘„ ^[na»¶¼QØ*ƒ×nèºåeæ| ë–˜ DÞí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zjõ»ù� 6aÆIvþ9¿_6xÝ>ä÷lõˆ¿è>�â�Ù“JŸN±á~³Ž{Âÿùõ[ öòn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒ[¿³WL³³¯<ÖÆ ;¨(l¸²e[0üãݳ6]ô{wżÂV)6/4{ä#{ŸåðÒå…û7y·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üj©‡ßu[^² ïý7û¯ß|´(lðºmð[5âs{N§˜|²íÚøRaË¿>ƒ_2„ ^+O/<ØÉ»ý4@MàW ~µàW ~µàW ~µàW ~µàWK½üîÚ½Óî]:ÙN»òX»tøÁEaÄSö·‡‡}ÔÖ�<¦ëtŠ3¿l[gŽ·Ý;¶¶.àwžXô•â°á¹_vݳŸ’wû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–zû]»i…ýæÎŸÙ¹cþ¤(lðºiðûíùGuÏnØ8ö;¶ý™™…- ìÜb¶øÛÅaÃ’»–÷Còn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ¢ð»cçv›úäx;µí¿Ù%­‡… NÙÏö¶nä织ÍWýÊv®[YغŸÁ°ô§ÅaÃãÇ™½»®°Bÿ!ïöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-J¿Ë_ÒμãÇ6ú·GÚïZö+ &9Äžñéî°aýéÇÚÛ÷]l»ßÙ\غ“ç[ŠÃ¿†Ã¶5…ûy·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjQûÝúî»náy6ìš¿±‹N;¬(lðš1ììÕÖ¿ê6žw‚mâÎÂÖ�¬¹ ë.!lð»Sø…#û y·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉËïkgÛðiÿdg_üß²QØpÍÐCmÉ©¶gvCg½yå/lÇÚ§»6~ý–â°¡c€Ù¦Ž®Çú8y·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÓïÆ­ëlüœ6äºãì‚3�( ¼îiý¨½Üú—{‡Ó�µ·n?§ët <`aƒ@ôqòn¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ²/üÎ^1͆N;Ñκìsvù�ŠÂ†«†jO¶~ªhvÆsŽ·m n1ÛüdשqØà§VôaòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-ûÊïk›WÛþ‡ ¾þx;ÿœß/ ¼¦Ÿy¤­m=º(pxsüOlÇŠ;». ¯†öÚ÷ÈÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µìK¿»vï´»Ÿ¹ÖNžòvÆåGÛ¸a… m§°…gG§Rê­)ÿaöèWŠÃ†gÞu[Ì>FÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¥/ø]½á9}ß/픿aç�þâ°ÁkÚèÏÚ‹§ï¹3…ׯß|Ív>ôÅâ°añ·Ívn)ìµo�·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjé+~wìÜn·=q™�tÛßÛÈ _´K‡\6\9ô}¶à¢oغ‘Çì N;ƶO9²8lXô³í¯öºïÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µô5¿Ï½¶Èθë'vÊMß´óÆüqQØàuËèÏØª±ß*šÝ°õªè‘^~ ‡­Ë {Ü·äí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zú¢ß­ïn±¶y£’Ù §¶ý7»dÄ!EaÄÁÚÜ 'Úç×6¼ý»�›=¸ßž°ÁïN±åñÂ÷yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¾ìwÑšY6üö¶ÿ¼õ[6ú·GÚïZö+ n:÷Ó¶êší¶\ò§¶û�ý÷„ Ì6>XØÛ¾!o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒ×ýnÜºÎÆu Kf7 ¿êËvÑi‡… ^³¯hëÇý( ÞûiÛ=ã€=aÃC�?¿vCaoù“·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjé/~g¯˜f-íß³“ný;;çÂOÚø–ý‹Â†Éçi+¦�jÎý†m:÷s¶ë¾÷î :k÷Êßö”/yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–þä÷µÍ«mìÌÿ“ÌnrÝqvá( ¼¼þ'¶aêHÛxÎçmç]… ;çþcaOù‘·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjéo~wíÞiw?s­�<å’Ù g]ö9»|ÈEaÃÕ§}ؖϾÄ6Oú‰íhP6¼{ÇŸÚŽ5‹ {Ó“·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjé¯~W­_j£ïûe2»aðõÇÛoG}¸(lðº{âwmãÜ+íݩŷ¿|wêáööôÓm÷;› {Ó‘·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjéÏ~·íØj·=qY6x�qùÑ6nè�EaC[ëì™9ÛŽ™Ç… ;ïx¿mú¯¯Ø¶·˜íÚYØcýÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µ4‚ß%¯Ì³3îúI6œrã7ìüÑX6xM÷uÛ´à_‹Ã†»ß—\8òÍñ?±«/ì­¾äí— j¿Zð«¿Zð«¿Zð«¿Zð«¿ZÅï[Ûß´¶y£ºg7Œ¼â»løÁEaÕC¶'§ýÐv=¸çö—~wŠ7ÏÿLrkÌ-·Ž°][Þ(ì±>äí— j¿Zð«¿Zð«¿Zð«¿Zð«¿ZÍï°á·ÿs×솛¾iç�÷±¢°Ákêo?coÜqHwذû�ýmó…ŸL† çoïÌžT·Ó)òöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-�èwãÖuvéìÁݳNmûovÙˆCŠÂ† ƒ°G'°�3»Â{p?ÛrÙ'’°ÁkÓE?°wWÌ+ì±vòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-�ìwÖò)ÖÒþ½$l8ùælôo�´ßµì_8ÜtÆ{íµ©…°¡³Þ¾ò£Ýaƒ×–É'Û®�/öX=yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–F÷ûÚæÕ6fÆ î٠ïú²]zÚï… Zö³9—¼ÇÞ�Ñ6l›ü1[Ú1ÝaÆ3¿l[gŽ·Ý;¶öZ9yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–fð»k÷N»ëé«ìä)ÿÐ8ÜúwvÎ…Ÿ´+R³&�|�­½µ+lØqÿ_… ^Ç~Ƕ?3³°×ÊÈÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µ4“ßUë—ÚYwÿ¬{vÃ�뎳KÏúý¢°ÁëÁ ßcÛï�íž÷Û|Å�ŠÂ¯ÍWýÊv®[YØkyòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-ÍæwÛŽ­v󢋻ßÝ0ê’£ìŠaï+ ®=õ=öü �Ãö…GÛö…“lùß(N?ÖÞ¾ïbÛýÎæÂž³ÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µ4«ß%¯Ì³‘wþ¸;p|ýñ6o…yÿùï±·fþ‘íÞð¨½}Ço’€!6žw‚mâÎÂ^÷&o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕÒÌ~ßÚþ¦µÍµgvCg�yù_Ù„S- Ú†½Çž›<ÀlS‡í|m¹½yå/ŠÂ/_¶cíÓ…=ï!o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_³ù«î³á·ÿóžÙ 7üw»üüÏ… ^ÓÏ=À6¯ø]²ÍöÅ÷ØÆ1ß.N?ÖÞºýœ¢Ó)òöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øíbýÛ¯Ú¥³Ín8kâ—lRëáEaä¡ï±'§ýÐvíÚa»·¿�\§ÁoÎ9ÞÞ¾ë|{süÿ´u£Ž·Mã~dï¾°°ðLZ &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ÅÌ\v‹µ´¯;l8å¦oÚ]6xMóǶñµ¥É6;ׯ¶Í×üa}kôsgmõ·öî³³“õ•4@MàW ~µàW ~µàW ~µàW ~µàW ~÷æå7WÚ˜ƒŠf7œ}ÍßÚÕ§ß™bBËþöè½#“Ù Ž ›.ø¾­ùÅ¢�!Ô¦‹NLÖSBÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÍf×î�6mñ;yÊ?t‡ 'ß|‚µ]ô'6¡¥xvÃ-ÿõçöÚª¹Év»wl·õg}53hðk8¨!h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯ü–çù7–ØYwÿ¬hvù×}Õ®;퀢°aÂàmîô{wÛÛtÙ�2ƒ†�¿ý^a¯: &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿=³mÇV»ñ± ŠÂ†Sný;»þÒí5»aò9GÚ‹^dFW2øïÛ—Ü_Ø£‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁoå,yež�˜þ/E�Ão¯ÿ¼Ý4ªxvƒ×~Ùnþ{vUË�vÓ°ÃíÅçö¢… j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð[omÓ&Î=³(lh¹í›2þ»rðž áŠ(tðjq¸½øôôÂ^t4@MàW ~µàW ~µàW ~µàW ~µàW ~kcþªûlè´‹‡Koþ+›2zÿ½B†P7�÷ÙÂÖ: &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿µ³þíWíÒÙƒ‹Â†áS¾i†ì—4\1øÀ–: &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿½Ãoƒ9sÙ-E·Á¼ø´Ã2ƒ†Îþ£ÂV: &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿õaí¦6fÆ $h9á‹6~è�E!Ãå�¿ÏºñÈÂÚ:z Ø]Ë–-+,54hP÷òŽŽŽÂRýòJ¡¡jÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üÖ�;·Û´Åì?§t… —´jãïo—Œ8ÄF^qŒ�žv|aMeƒ†¶¶¶îA¾ÿÜÚÚÚýóرc“Ÿýqõòj ¡jÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üÖŸaS¾Õ}E\'w–šŠO�ð|àï¿üwŸí ^^ 4T-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚ßú3枟d £îü—Â:*|†�Ï4pÒ <P/¯ªüjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁoýYòÊ<ÖþÝ¢�ahçïO¬�]XCGEACüÔ�B©åÕà •¢(Š¢(Š¢(Š¢šµî}ô6;㎟Ù)S¿k£îùE.!ƒÓcÐà~¿ cŒñÀ?êåÕàRA~µàW ~µàW ~µàW ~µàW ~µàWKÞ~Ë >¸÷�!=È÷Ó(².Ö¨^^ 4T-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû-4ø) á“¡Bè�µÌQ/¯ªüjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ýV|1Èþ U ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚¨ üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µàW ~µäí— j¿Zð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µàW ~µàWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¿ZòöÛpAÃË7Ú±c³#†Í¶cÆ<šüõ‡Ž@ ~µàW ~µàW ~µàW ~µàW ~µäí·¡‚Òaï9iVw>lŽM_òFa ¨tZð«¿Zð«¿Zð«¿Zð«¿Zð«%o¿ 4üõØÇŠB†Pžm_¿ô‰¤~zÝRûùägí—7>g§ß½2©13W[ÛüW’òP ¯åë¶ÚÊõïØš�Û Ï:-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·ß† ><âáÌ ¡ž5`h‡yÖ¼¤ü�f 0è´àW ~5xÿÆ©kzh¿Zð«¿Zð«¿Zð«%o¿ 4|¾ó‹mV8Ъ¿ $´à7ø�¬þx[åÔµ| ýj ÿÍÚ¯üjÁ¯üjÉÛoC þ…ö°Î/¶ñÝ�_|ÇÍyÉ_»%ùÒàuâ׺êað>lú É€ÞëĉKºúG�^ÐÄûí/UÏãæÇ_g !Ä]ãW‹;n¤�„.Ó÷uq…Ïv\ÌZÓýù�+ô…qÅýb\¡9¨evQÛ õÞSfw¯“µ½ï7ëù²ŽË�7ý¼²^¯{ÈòÓŸñ×Å@Xƒ»¤ÿÍZð«¿Zð«… ¡—ø—‚ÏŽ^`�ò�}rÔ|»å‰× �Ô�wvìêþÒÚ¬•_ŵ¬òGCYåýdÖ{ªTPvH§sß><§ÿŸîÇyÉìµÉkóïî£ãùMÝ¡< $´àW ~µàW AC�è� •ƒ¢¨~[ÿ™±Œ¢š¸>2ò‘äÿâ0›ð»žJþÏŽg†ÿÛï^º>ùÿþ©—ßJ¾¬{ëÝÂ7ƒÆƒ�„üjÁ¯üjÉÛ/ACP¯£Ô_|¨:5ª‚ ƒ—¸üó>«qýøêgº?Ï¡~uó²îÏ{\—?üRwÊûˆÐ_ĵôµ·»û”P×,|u¯S×Úa澼׺¾}Ö~ã>)”WÖñúëH¿6½Yâ6”{Œ�•jòê£ýï�'?ÔÝfC{Ÿë–iÏ'Ÿ…pÚãµ�ŸÁðY Ÿ5ÿÿ¿¯Á÷3-øÕ‚_-øÕ’·_‚¨Ÿ¢›5�PœžÒŒTâ7¬õTñ®§JîÊUzÀW®ÒÁr {ªxÀØSuû,1�8 ú"ª/ Ü_Ù¼½ð®÷ò8uM�{O¿}!9°ÓiVû�+½M¹ÊzÞR•>îr•õÚKUÚ]¹Êò_ªÒï_¹ dõ¿~�§ Z“ìóþç6tËÙ÷®JŽÿ×·-O^så¿rÁ¢ÄíÇϘ[´Ÿ¾P~W.?¶£Ï[˜ë ãžÜë= }–»ð×¼põæÄQ½.0íûä#zøþ«¿Zð«… ¡NÐP54Â@¢/ƒ_Y ‚2 ô¿õ‡ö«GÑÿú Ýë>h÷�u¸¶E¤ZïÚÈúàßC<è‹×f A•‡*~¬?l{:9v]üõx½^Ôy(ç×»ˆ÷ÅÅ65ÐÿjÁ¯üjÉÛ/AÔ~µàWAN>Ð~5Ð~󡯵ßpú£ß* Þý4 Ìûi>ÀÑ ³dŽ»øñîPÀO¿ˆø}©¸+Mýð×ÅŒ=üÿ¦¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-�ê×/0éƒép­&¿¥Äý:*a°îªô�¼_¸ÒúánÍ|M“F¾+ Tý¯üjÉÛ/AÔ~µàW ~µàW ~µà·4;víîäú-=}€ë3k|€nc.*ýÓë–vÀßÇŤ÷Iyøàï…_84œÖn1íïa#ßÝDýƒüjÉÛ/AÔ~µàW ~µàW ~µà·þø_Ö³®1Â]iúFùë Aöën„[°†Yî?\´/ÞÉ$Oè´àWKÞ~  &ð«¿Zð«¿Zð«¿á#ýù®4õ¬ƒ[º®­áׄw0ñë„økñ×éï­;p7ýñJå Ђ_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁoýÉš12`Èl;㞕ɠ?\‡#\4Ôà <,ðÐ ÞNU~‹ÕO�šŸ„%áZ~L~AS'üš!sW½™„7[¶í,¼²¾íW ~µäí— j¿Zð«¿Zð«¿Zð«¿z3c$ÜÙÄO�¸ÿ¹ ÉÀ?\sÃO£ðÓ)ÂlмN+ñ»¥xâ$õçö`"ÜFÕg†øŒŸ5ñÔËo%·‘Í Ú¯üjÉÛ/AÔ~µàW ~µàW ~µàW ~µäå×/$n¹ê˜ôpÂ/8éžôPàÛãÛW.X”„†ßCUá!Lø�Sü8üâ¤çÎx19>cü"¦ªlܺ£ðJ*Ã_'·ÕCÿ %o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕÒWý†;–„Û«^»ðU»döÚd†‚_£"\“âèóÚÇϘ›$(ʃ Nn???&?6?F?V?õä�ÁÜ>4è´äí— j¿Zð«¿Zð«¿Zð«¿ZɯÏ:ðpÂg!ø Þg%øì„p UŸµpÜÅ�'a�Ïfˆƒ€<êC#NNQñð¤ÙïÆQ/è´äí— j¿Zð«¿Zð«¿Zð«¿ZšÝ¯_¯Á¯Ûà3ü:áN~}‡Ÿ^·4™¹à§>x8á×�È j-¿Ø¥‡~úˆÏ‚ð‹[V{ŠF³Cÿ %o¿ PøÕ‚_-øÕ‚_-øÕ‚_-øÕ‚ßêð;\ø¬ üÎ>kÂï„Ñz×ÊdÖ„ß!£·×˜ð;nxÀáa‡_dÓŸ'Ï Xö'h¿ZòöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-ø­?Y·}Ëlûç¶§“;rø5j¹M¨~�IŸiá§„øE5}6†_Ï¢Y¡ýjÉÛ/AÔ~µàW ~µàW ~µàW ~µàWC%·]úÚÛIXà3"|6„ßu£–kGø)^øl ¿…_”Òo=ê³/Ú¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µÔâ÷•ÍÛíþç6$w²ðÓ'üÖŸùHfÈÐSù9|û“¦®Höç×¢ð[�6 ´_-yû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üj©§_¿P¤ÏTðëBøÌŸÁpÔè™COå3'|Å/o|.™Qá30–¯ÛZx¦þíWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zòðë×hðk5øé~í†_ýLr§ŒZ.Jé׎ðmýZ~÷�¾~;NÚ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µìk¿~‡ ¿k…ß½âW7/³ã.~<¹«EVÈÐSõÅÛqÒ~µäí— j¿Zð«¿Zð«¿Zð«¿Z𫥯úõë4xPpùÃ/%×oðáȳæe =Õ¾¼'íWKÞ~  &ð«¿Zð«¿Zð«¿Zð«¿Zú›_?EÂO•¸aÑkÖz×Ê>;NÚ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µ4’ß¾x;NÚ¯–¼ý4@MàW ~µàW ~µàW ~µàW ~µ4ƒß}q;N_î³%ŽÖuñJÿêOÞí— j¿Zð«¿Zð«¿Zð«¿Zð«¥™ýªnÇù«›ŸÛëTŽÃ‡ÍInÑ õ%ïöKÐ5�_-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚ß½©çí8ãò»b@}É»ý4@MàW ~µàW ~µàW ~µàW ~µà·:z{;ÎÆ=™Üz3ët ¨ž¼Û/AÔ~µàW ~µàW ~µàW ~µàW ~ëC|;Îßoíùú~ÁIB‡Þ“wû%h€šÀ¯üjÁ¯üjÁ¯üjÁ¯üjÁoýñk16¬ønþç,; ³âeÝ�E¡Ãš�Û {�JÈ»ý4@MàW ~µàW ~µàW ~µàW ~µàWƒ‡ Ÿ½À:å!ûä¨ùÉõ|悇 *x¸�:xùé~j¡CÏäÝ~  &ð«¿Zð«¿Zð«¿Zð«¿Zð«¥”_B‡ú�wûí1hX¶l™ 8ÐÚÚÚ K,ùÙ—…Š4hP÷òŽŽŽÂÒú-¯:-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕR‰ß:|wÂS{Ý3.B‡½É»ý– |€ï~ÒACü{À—�;6ù9lëÔky5ÐhÁ¯üjÁ¯üjÁ¯üjÁ¯üjÁ¯–jýnÙ¶Ó®]øª�8qI�¡ÃÙ÷®²åë¶¶lNòn¿�:á¯@ú÷€ üwŸQ¯åÕ@G ¿Zð«¿Zð«¿Zð«¿Zð«¿Zzã·ÒÐá˜1�6mè�wû­9h§5ħ6¤�ÖÖÖä±z-¯:-øÕ‚_-øÕ‚_-øÕ‚_-øÕ‚_-øÕR/¿„ÙäÝ~k bÚÛÛ“`À!h€þýƒüjÁ¯üjÁ¯üjÁ¯…_ü.?½n© Ú‘8x5Cè�wûíuÐàø¬Ç‚8�A½–Wƒ‹ì«ù“õY¤(Š¢(Š¢š£?ó¬]vÏ“öýqóí�Á¥ï^ñ‰ÓçØ ë³ö‡—dî§?WžÔeFƒÏ8püŽYq¬×òjÈ[$ôè´àW ~µàW ~µàW ~µäé÷�»lêâu=ÎtøÔ¨ùÖz×J{|í––ý—¼ÛoÙ ÁC„øZ aæ‚ ée�xy< ¡^Ë+…ŽJAÿ ¿Zð«¿Zð«¿Zð«e_ù�C‡ Ÿ“8xõ÷Ð!o¿Íhè�Ð@)è´àW ~µàW ~µàW ~µô¿;ví¶ûŸÛ`¿¼ñ9ûðˆ‡3¯:t<¿©°eß'o¿ ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjék~+ >~Æ\;iêŠ>:äí— šú-øÕ‚_-øÕ‚_-øÕ‚_-}Ùo#„yû%h€¦ƒþA ~µàW ~µàW ~µàWKñB‡_ß¶< ²¯¾:äí— šú-øÕ‚_-øÕ‚_-øÕ‚_-ýÕ¯ (ô:øl(<¨Øäí— šú-øÕ‚_-øÕ‚_-øÕ‚_-�à·’ÐÁO½Ø¡CÞ~  é Ђ_-øÕ‚_-øÕ‚_-øÕÒh~ûZè�·_‚è 4ȘԲeË K»˽|½˜ÖÖÖ¢ÇÓÄ�ÅÛús¤÷ÕÞÞncÇŽ-üÖEGGGò}?V?f¨ú-øÕ‚_-øÕ‚_-øÕ‚_-�ìwáêÍÉí0ý¶˜Y�ƒW:¼³cWaËú‘·_‚Øç´µµuð=ðßăi_/ ü=(ˆ×õP  ÊmÛ_‚ ¥ðc%h¨ ú-øÕ‚_-øÕ‚_-øÕ‚_-Íâ÷ñµ[z  í°Ÿ^·Ô¦.^W·Ð!o¿ Ð+v½ùªm[4Ͷ=:%ù¹â !ÆÑYƒ||Oœ8±lPn[ª |]ß6Þ&ayø=kݰžWÖkuÂãa»ð»WTÒû!h¨ ú-øÕ‚_-øÕ‚_-øÕ‚_-Íè7ÏÐ!o¿ P3ÛŸ¼ËÖŸõUÛpÖßtÕÙcÛŸ^x´:ÂàÜ€€®ã ø�=:ó±@¹m=¨&hˆ�Ë·ƒûôc¥ÖõåéçJSêx}ß—ï3à�4ÔýƒüjÁ¯üjÁ¯üjÁ¯–f÷B‡£Ï[˜8xõ&tÈÛ/At³}ñ=¶ñ¼lýÈ/Èkù߰w¹¾ðÌ]øÝÏa°_.,ˆƒlûv•n[mÐîãuÒ�•Z7ëX|½p̾�—ÿœ|Y ½ßwz}¨ ú-øÕ‚_-øÕ‚_-øÕ‚_-øÝÃòu[íì{WÙ1cÍ ¼BèpíÂWm˶�…-K“·_‚èfÓE?È Tµá쯞y!lp²þN�ǃz'^¿Ü¶Î¾JáëÇÇEР�þA ~µàW ~µàW ~µàW ~³©$t8¸e¶�8qIÙÐ!o¿ Ð�Ï0Øpæ—3C�º×éÇÚÛ÷\�<¯˜Ãé>Xí:ø²€¯ëåøzñÀ;zÚÖñíã�zúwÇ·÷ý„ãó !¬“J­ë‡=áë†}ÄAƒï'<_x}éã…Ê Ђ_-øÕ‚_-øÕ‚_-øÕ‚ßž©%txpùF;vìcvİÙÉvþ{4@MìÚø’­?ë+{~�†�ëWÖªŒ0hÙNú±x`ïø <~<x÷´­?ž5ëÀÃ'Â󤃅¬ß³ÖõŸÃóÄaGÀ—e=–‡c ûñçðe µAÿ ¿Zð«¿Zð«¿Zð«¿Õá¡Ã˜™«í¸‹Ï ¼:å!;à䇊–>lŽM_òFa/: f¶=65 âÚ6ÿ¦Â£ÍG:x€¾ ýƒüjÁ¯üjÁ¯üjÁ¯üÖΚ�Ûì‚Ykʆq}vô‚–: WøÌ†wæÝ˜Tµ3 ‚†þýƒüjÁ¯üjÁ¯üjÁ¯üÖ‡JB‡O~¨°¶‚h:è´àW ~µàW ~µàW ~µà·þüùo²o•ùÉQó kè h€¦ƒþA ~µàW ~µàW ~µàW ~ë�_‹á°asŠB†Ã‡vØ-O¼^XCA4ôZð«¿Zð«¿Zð«¿Zð«Áÿ&ƒ_Òg2ä28 ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@ÓAÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐMýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/A4ôZð«¿Zð«¿Zð«¿Zð«%o¿ ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@ÓAÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐMýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/A4ôZð«¿Zð«¿Zð«¿Zð«%o¿ ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@ÓAÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐMýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/A4ôZð«¿Zð«¿Zð«¿Zð«%o¿ ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@ÓAÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐMýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/A4ôZð«¿Zð«¿Zð«¿Zð«%o¿ ÐtÐ?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@ÓAÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐMýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/Aìs–-[fì®AƒÙC{{{ò˜¯ëø¿ñzé}”[×ñý�;¶ð[ÖÚÚZø­oáÇêÇ õ�þA ~µàW ~µàW ~µàW ~µäí— ö9é ÀûéAµ/óÁv[[[ò{VÐÿý%hðã-AC}¡Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€Þ±m�Ù+�ƒÿ—'tý\YAƒúþxX‚€ô6YaB&ª |]ôÇÛøc¾~X~ÏZ7¬ç•~Ž@xýX©u}yú¹Òį'Æ÷ð}…PÅñDZ@ï¡Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€=¼vƒÙ#1›ÕÙ,Ôõð‡ÍÖ\� ÒÓÛ¤wB˜Pî± âç�×I?VjݬÁ×ó ÀË·óÊ â !½ßwz}¨ú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·ßÎ_cBC­�Ge‡ªê�8;¨w=t Ùó-ÉÓ¦ƒ`‡A|ÖÀÝ×}à�жIï#ÌðåŽ?ÔÓ¿;émüÂ:YACÖº^ñqô„¯ö ¾Ÿð|þ¸?–>^¨ú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·ßÎ_cBCóÎJ³Ùïß;@ðë4l]^X©2Â@:®€Ä}PãÝ¿à‚ ŠôåöˆK‡Ž?O˜eà뤃…¬ß³ÖõŸÃóøþÒø²¬ÇÃòpla?þ¾Œ ¡~Ð?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@í¼<±sÄ= ªCÍÖ^Vx°±Iп Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€Þá3Ö^ÒUUÎdèÏ4ôoè´àW ~µàW ~µàW ~µàWKÞ~  é Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€¦ƒþA ~µàW ~µàW ~µàW ~µäí·Ç !\d/ëÊÿá‚v>�< ^^)4T(ýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛoÙ Áø>à÷�!üçpµü°Ž£^^ 4T(ýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛoE§NøÀß+àþ»Ï|P/¯*”‚þA ~µàW ~µàW ~µàW ~µäí·æ !øûÕ÷=P/¯*”‚þA ~µàW ~µàW ~µàW ~µäí·¡ƒ†¾Z�?YŸEŠ¢(Š¢(Šj–ÊN�€¦ƒþA ~µàW ~µàW ~µàW ~µäí·¦ Á/Ô˜u±Fõòj ¡@)è´àW ~µàW ~µàW ~µàWKÞ~Ë íííÝ·˜ ˆ—ų ÔË+…† ¥ Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû­hFC„† ¥ Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€¦ƒþA ~µàW ~µàW ~µàW ~µäí— šú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚h:è´àW ~µàW ~µàW ~µàWKÞ~  é Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€¦ƒþA ~µàW ~µàW ~µàW ~µäí— šú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚h:è´àW ~µàW ~µàW ~µàWKÞ~  é Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€¦ƒþA ~µàW ~µàW ~µàW ~µäí— šú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚h:è´àW ~µàW ~µàW ~µàWKÞ~  O0hÐ 8p`RË–-+,í",÷òõbZ[[‹O?oëÏ‘ÞW{{»�;¶ð[6ÉsV‚ï/^×÷íÇÑÓö¥ž#½?¨ú-øÕ‚_-øÕ‚_-øÕ‚_-øÕ’·_‚Øç´µµuð=ðß>0÷vÀ× ƒm âu}€å¶­&hðýª ÒÄÏï3Mož*ƒþA ~µàW ~µàW ~µàW ~µäí— zÅÆ­ëlþªûlîÊ»“Ÿk!bJýßé'N,;/·­‡ • ¾~(?Îø¶¾,~Žtør< Âú銃’@ú9ÂqÆû Ï•>_ïßËãcó �9¾¼™ Ђ_-øÕ‚_-øÕ‚_-øÕ‚_-yû%h€šylõLÜþ}2-Ô?Ú‚U÷­Ž0`ö àƒð¬�¸ªG��ùX Ü¶>ÀîÍŒ†xØÃcá5øï!pâýÄ?§IïÇ÷áÇï/¬ãÿ:éàÀÉZ?ì3ö¯×,Ð?hÁ¯üjÁ¯üjÁ¯üjÁ¯–¼ý4@7‹Ö̲‘wþØNºíïå5bú¿Ø¬åS ÏÜ…‚}0ûå‚8hðA¸oWé¶>°îMÐÊ}}ßÎI?–þ=ÞOü³¯Žß·IoŽ+^ž^'>ŽÚx•Z?~ýñ¶ÍýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/At3ú¾_f†ª:íÄÂ3ï!„ N¹�¿?œ�xýrÛ:})hH“Þ.W¼¼Ôqx¸^C¹õÿÝ—§=4ôZð«¿Zð«¿Zð«¿Zð«%o¿ Ð�Ï0hiÿ^f(Pï:yÊ?Ø´Å’çõ�±ü�xàB_ðuÃ@Ú׋g-ÄAAOÛ:¾} œôï�}4øãÁ‰ïß#Þ_©ãˆgrø¿¥ÖwÂ㱓f�þA ~µàW ~µàW ~µàW ~µäí— jbýÛ¯ÚàŒP¯ӰnËK…µ*#„¡ÂÛI?–,{8?=mëÄ�Ç¡EŒÄÃã¥øNú±ôï¾�@¼Ï4¾�?^[ØG¼¿rÇáÛxùö¥Öw‚ŸØY³@ÿ ¿Zð«¿Zð«¿Zð«¿ZòöKÐ53oå=I°à§@xùÏsž¿½ð(ôX�¶aÀëø : ¤}`ëëÆËÒ¤÷åã¬åaà]j_~ >ˆîéñx¿þs)üñ€o翇Az°‡åñ~|YðïÃIÿ“Þ—ïÊxaÿ×INÖúaŸ±›x½þýƒüjÁ¯üjÁ¯üjÁ¯üjÉÛ/Atsâ×ì##±÷œ4K^ñ°]0kMò¼>p ÿ4>xMlãnšRûJ/÷í}ߥöå�ù๧ÇÃ~=ÐÈZ/‡éA¹o›ñ²t°ÿîëøï^¾Mz_¾žxyz�pŽ¿¦°¿RëÇNãmûôZð«¿Zð«¿Zð«¿Zð«%o¿ ÐÍQ£d†ª0´ë¯çåÂ�0Ø åë…Asåö/¿—Ú—?—ÓÓãñ~}™Ƴë;¥øéåN¼,Þ‡“þ=&½¯ð:â奎ÓðšË­ïøï¾<Ëy_†þA ~µàW ~µàW ~µàW ~µäí·¦ Á#>È Ä|à–ûz�z-¯jõø ƒƒ[fg†õ®O~ÈZ¦=Ÿ·\;MAkv²|•óˆ=úöñÿuô{ûªÄ¯ÿÚ¼·ÿx}w¾Ï53=µ»t¿ûuzê7š�´»Rm0TÀ×q·•ô ñg¢Yp᳜ú¿ŽûÎú\§Ý;éöë?û2÷;íM_ÑAƒ py¡‚—–Å"¡:Ü]ð>à±ÛPÍöA¯þá ã5ள–Ciâ6ë:åx™Wh³é>JSÊczyüe §6{(ç±Úv {H; _šÜUX\Çí5ýXÀ×Áså|eù�I{ŒÛ8ßÛö&í+ËoºOˆñuÂrÚïJµ»°,”û/å·Üç I»p?N©6˜µ®ÓSŸ�þL4!4åmÒ‰Ý†Š½gyÊêCœx½ùÞÖP3`ßBÐuƒ êAÔ ‚¨ P7 n4@Ý h€ºAÐuƒ êAÔ ³ÿo_„Q³lü²IEND®B`‚sogou-srpc-ac65014/docs/images/benchmark2.png000066400000000000000000000704651525217424400211010ustar00rootroot00000000000000‰PNG  IHDR) â[fsRGB®ÎégAMA±� üa pHYsÃÃÇo¨dpÊIDATx^íÝ ¼Tåy÷ýöijM46iÓ6m’6MÒÆôI›´µmšäÉá©oâÛæ`ò$­|ÒCò¼i›Ò´M”“²=•�ê1$¨[#â•�¨x (l×öš™5kfÏuïu]³Ö½~δ �Ð64@ÛÐhmC£´ �Ð64@ÛÐhmC£´ �Ð64@ÛÐhmC£´ �Ð64 QwÜq‡3fLEôõõ•nEj¾ñ�oTü.�<òÈÒ-£‡^³^»Þ �. �(5ÂÂTA£!]ùFƒb4 g===¥¥íãßß°‰ñÔSO¹±cÇfËõ¯~NUøþõöö––¦+|=çœsNi) 4 Qa£!Ö`˜2eJùö|¨�ëïï/ÝóuƒƒƒÑ‚×?föìÙ5‹1­SÏ®CßR«PкëѺõZü7Û €*œy¼zŸýkoÆÀÀ@ö>ùÂÙG½ß›~7áý}ø÷]ë�Ñï:ÜVýÎõ»¯vÿzÂíˆ}6«iô3çoSxÚÖpy«ÛÞnùíª*ÊÃÇè÷’:k¯F �¨z�†°¬*ÐB�8*âòE«~®÷|õŠ­#_$‡1ZŠ�ð}lTµfA*ÄU�æ5ò9ÉþµšXŠV„�‚jM‘P³Ÿ¹p¹~æô¹nþ5kBÍ6ÔP¡Ñè&4 QÍ4´ÓîÝpà E]XDVÛ¹×òð1ùC³Ã#ôÿðùôÝ¿^± û„ë÷ŦŠãFoEø{kDØdÐï(ü}ê½ ›º=dHìs¢÷>ü}(üã´~¿L¿k¿\ÿês¨çh–>“~�ùæW5Í~æü}Ý¢ÑmÒë©wßð>rÅÚë€Ñ†F$ªÕFƒ„Å© 2¯ÖÎ}X`†Åd~y#ßFÇøu(Z]‡áï­ž|;bAÂu†¿o©õ9 �0ÑéÞ_ ‚v›�¬³•Ïœ¿¿¢[4ºMùßsL­ÜM‘µ×£ �HT‘FCøØp'¾ÖÎ}µb'üfÙ£­×�ßÞjt¿üaüú9öx¤º]Û¨bÖÑjºh™¼~Î 3áº}~5üíú¿Þ¿=ùÂ:ü½ÕèùB(úû)ÂÏC­ÏIx›ßÎð³£PZä÷¾�µš%^«Ÿ¹ð1¡jË›ùýŠ_žÿý†á‹÷N7†@Z¾_�|.•ÕÖ§ÈÿÎjÝW60D��Ý/\O~,t? �(~G<_¬Jµ2üö¾Vá‘ß¹ŸOE‰4R5*¿.±B6,6µ-þv­Ã(ú7äïŸÿ:â=üæ7lBø×ÝìóW{mŠüï.ü½Õ®GÏQKµûVûœ„G(|�˜_îCë©· 1aÑÝÈãuŸðyUí1±åE?_zŒ-a!�Ï©ð1µ4òšó÷ÑûªíÖïKÛã—‡Ÿ·ücÂð÷ �4ð¯Ië ׿‹î§fT¸,;ÂFJøüzœLxEþ}t? �¨pg<,¼°€ŒEX8xᎸs¯oou›ÿ67_¨¥õ…á7Èañ– áûÞæ—)Â"Ì‘á7õú¿.÷‡é7ûüú×/Sè>þ¹õo(ü½Õ®3¿yÕî>Ÿ_®í ú�„t{µÏ—Þ›FéµûÇ埣šü{Ù¨j�‰-/úù §ákT„ª-Ïkä5‡÷Q Tí4„üzóŸËð¨Ý ›¿-Æß7|þð}Οæ]ø@h4@¢Â‚'¶£_­ôI^¾ðˆEXHæïß.*pbÛï·¹Þkó6'Âå1á‘ aÑ6>|Ùìó‡ïS½¢)\w=þ~ŠØï3Tí¾õ^KøMsžÖ‹>)<%<º&lîÔ¾—ŠFU{Lly»?_Õn«¶<¯‘×Þ'ÿ«v[­ÇH8ÆÔŠpLPŽèqµÞC/¼�¶%ToÛÝ�F$ª™Fƒß‰ ;E¾€ wîÃP±©o¸óÅ@Xœ+üÑí¢ç �¤ðÅh­"&Œðõ…Ë« › z¬^�ÿ9<ä»Ùço¦h ×]�¿Ÿ"î{(|Šð÷Tíµè[q}®|s¥­/<”>ÿ�z5Õ¿¯¥ÕÏ\ø˜Ply»?_Õn«¶cÕn«÷¹l´Ñà�FÐg%Ì¡já…¿mK¨Þ¶º�HT+� ×hARK씊vŠmoµ×V‹¿¿¢šðpmý?lÌ„¯­Ùçoæ} ×]OxßZGhÛýýò§(´ò^Æ„¯QQOØüÈoS=­|æüý¡ØòVÞ“Øz¼j·U[ž×È{[ë3Ví¶Z�‘zcL^˜?:Ê!lùå ¯Öû\oÛÝ�F$ª^Pm'^ß:úåùÛZÙ¹�WÁØÈ7àÍ_§/¦ÃùŠš¡|ñ~ã¾®fŸ¿™÷5ü½Õ6T|ÇÞûü·ÌùÏJµÏI³ÂרÈ aÇ#Þ¨V>sþþŠPly»?_Õn«¶_þs£ß“^_þw>O#ÅyØühôô¯•Ïœ¿¯"[ÞîÏWµÛÂ× õJì½??ùuxá}òÛVí¶Z�‘°ñ¦PãÁ7ut›òU¯Á �hðMAÝ/ül(¼üû¬ûjýzÃûǶ ÐÝh4@¢Š4TÔøÛ¾¸©WxT£õ…ES,‚$/|Þj‘/^óÅH>òÛÞVKø-®�XáÜÌó7󾆿·F轋öZ¡‚O¯Ïµ>_þs®/z_ê ?±æG#šýÌ…ËCÕ–·óóUí¶°0÷û|äó#¦Ög¬Úm�|.Ãf@µðÂßk>ÂßU(ß„ð.¯¶m€îE£U¯Ñ––žvÞýíþÐõ°PÈ‹_�¾‰ÔzÂõ*T0¨ òÍŒjTÐè9ÂÄ{Û~ÑcòϧŸõ~øo^=¿Þ°øŒÑvæ·!¿.¯Ñçoæ} ××(=—ß–Øû+æüvÔûœä©8Õ:Ãæ†žSë ¿ù¯%,°µÝ­jæ3Wí÷_m¹´ëóUí6­Cïex»ÏÅPø™ÔûSë3Ví¶F?—ú�ëv¿ ½ÇÚö|^ûû†÷Óû¥ÐÏùí×{ ßUøúüûë?cz@Zh4@¢üŽ»Bÿ‡ ­4êñÅœ_¯BÏ3RÂ&E¾`é£Ñ‰¢Ñ`S' ž¾}VÃA…~­o°;IG=ø×7RÛ:‹F$*l4ø á�®ð°té0xÿúŠœ6º�HTx~µ éÊ7tn»E~¾UÁiØD£´ �Ð64@ÛÐhmC£´ �Ð64@ÛÐhmC£´ �Ð64@ÛÐhmC£´ �Ð6 5úûûݘ1c²;vli©Ëþï— ”–v~9èNu *ðUèçõõõ¹ÞÞÞìÿº�šÒéå {Õm4¨Ø× y*üÕðôóàà`Ç—€îU·ÑÐÓÓS>}Aá�2ÈþºŸ�^ºWC�†°ÀW³A € ›7ov<ðAAAA¥P­ÜIMŸ:6ÂÂß7:½¼z¤�\ÒG6�Ë€ �Îåº�†pRFú~bH-óËÕP#@:½¼ „€ ä2�>ò°�\lñFƒ¨È÷s4¨è÷ü2Ex´A§—7Š�°�\ÒG6�Ë€ ]ÑhH!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�®õOÝíz}ÛMêÿ²›¶ðß²Ÿ¤‹¿É€ 4 ` l —�4©©0aÞÝw®ül9&]ý%·öñ¥{H “h4À@Ø@.ÃcÛóO”cÓö²F�bÝ«Ümo,Ç‚û.*Ç•w�íf¯þAÞvŠ;kÉørŒëÿ\E“ÁǸþÏ—ï3sÙqåÇk]~½ ï¿´ü|wl¾¥¼-·­+oãŽÝ[K[`¸ð7°�FC „€ ä2F3Ó¾°Þò̃U‹ÿÖÍ.ésïžQ.Þg­šZQüŸrã7ÝI þ1‹Éó¿m¤G^õ×å×5å†o”_ï9“Êï…¿GŠðýóï«bë®GËïù«¯½RúMþ&6Ðh(€�°�\F·Ù¹g{´øàÉ;*ŠW}+ï‹ÚykfV¼añ?õ¦-ÉÇ^{D´�N1¾Y6‡E–uœpÝ×Ê¿§é7ÿWù÷§#9üïuþÚóË¿óe_Sþ,ètÿ9yìÙ åÏÏ /ï.}ª€4ð7°�FC „€ ä2ÿ*ä|Q§‹ÿEƒsÊ… ŠÂ°ø×·ß¾xœ¶pl¹¨T�+<­„Žlð¯UG<ø÷@¡#"üû£f‰ïÂSj²ø÷[Íý–o¼ÞMÌÍÑ0¡ÿp·ô¡ùÙíáïhÕ#?+¯+<:ãÒÛ§—ŸÆÒÉåí �ÌЩás¤G_ýʯIŸ?ÿZÏ[~bù=O/ÑgÙ¿gwmYR~/uÚ‹ÏåÐNüMl ÑP!`¹œ®ç^|¶\ð<¹sSÕÂò–õW•‹§ëî½ \Tå‹ËÞEÿQ.ıbÍJ„Egx*€",þuš„ïT û÷T¡Ó+bŧb¸è›|5Žšû97åúodq'…Ÿ¹pž =¯_ÂfSxš‰ zÿ‡�&+§˜¨!ã_SØP O/Qc§ÚçÉ¿—Š‘ø,¡;ð7°�FC „€ är{í~iW¹@йèañ I÷|Q±äÁyå‚Cá …&ðóEŠ!÷Å‹"VàX‰‰ó/¿Î|ñ>~묋µðúpbC…åù¬ä±Nuð¿¯ðÈ™pÎ �2á÷áQ3ád™gÜrdù³déˆÿšz�þõ†Í15ýûž^6ÆÔ˜ôï3§—tþ&6Ðh(€�°a4ärX¼(üζ"ü&6,`áaåá·±ùâ_åÅŠ ÿ ÿ(úVL)¿?—ßqFÅ{·|ÂòûºæÑeå÷û¡§×Vü.^~åÅÒo Eð7¹1j6…Ÿ¿p,ðŸWEøYÇ�ðô5ì�ái>:Â)–ëáQ>:Z*–çá>: �#—h4À@¤M;ƒ½‹¾í&õÙM[øoÙÏÃIf¸Ã¯Ôï¤j‡ÕJ` wúUÐúÞ°ø¿ÁTX9¯<á!ÚŠð›Íð|óð0mEøífxι"ü]ð gzø›Ü=ÂùLtT�ϱðˆ¦ðô’ð¦áÑLáé%:Õ'6¤aã2<½D§pù÷ lZ†s•èt0ÿ^† Kk—a%—h4À@¤K;jr…ø¤þ/¸{»µ¼ó–ßIÖ!é~‡Oáwañ¯o¾üΣÂïT*´“>§¥/ÿ§gÍWñàßEøÞé ÿžªñï·"¼ Å?jáoòèžž^•%£ÃS³Â+²„§—„GfYºK8ûתÇàðô’ðè«pÒÕpüŽ#¯ôœ#ÙüÐ^4 `§H‹Ö9Ïú&ûøª“¯¥yY¼Zît†‡‡ß åw<ÃÃ�ÃoÑò;ŸÚẓÑ.áé%á<3áåeóGš…ciX܇㯕ÓKÔ”ñ¯)l(‡§—„W� ›Éáé%þê1е�­prW�™tõ—²æ€4Ñh(€� ;é›o�«Y¥éPz¢ÚM;yÇ_ùywâ•_t'Îý’›Úÿw¿–ÅYWÿƒ»èš.ÇÜkÿÓÍ_ð]wÍ¢wãÍ'¸e‹§f±|É4wß²•cóm¸ÇWÎrOÞ“Û±~±{éáU5ãÕ�6îU»“Âþ&#%:ÝÁÙáisáé%:mÂëáQsá•r¦Þô¯å¢?é#殊,ÛGÍý›òëÓkõ¯;Œ|ÓÜG~Þù«�øÈ7ÕÃð¿«|Xž`(ŠFCìÔ#KliMçÝkr.ílhg$¶³’�ïF–)Žºâ³î„Ku§\üWnú…Ÿ.GßÌO”ã²ý/wÕÙÏâš3?æMÿh9î˜úåX÷½?s�|H›NüS·íØ?6;¦êvôþMÝxö'ÿèvžÿÏuc×¥ãÝsW_7ž¿nšÛ½hFÝØsëÅî…;æÕ�×ÞmÌäãåMk¢�™}b×Ó¥O+:å‘�nÎä_u}ã~Ñ]vô[²Ÿ�Ñ.¼ kxzI8ÿOxÙßðô’ðô¿ð’¿V.Ã:¡+½ø÷-Œð�0« …FxÔaá#aä/?색JÑI4  Ñ }ó£Ë’髾¡ÐÄ­ìðyÅg³Â�Îÿ”ûñÞ?ç3·O¸ü3né>-¤ ¢£qü!ÑÆL,b�™XÄ3±Ø½ðìhs&/¬œmÎäã¥ÁeÑæL>^yr}¼9“‹×öì,�ûRSá¼q¿à~|ÔÏ•ãü½??tãi¥{è„p2c]*Ô´áeXÃÓKtjž/žu)Ò°ÈV>nîßTü=~=ì�ΘBä'\ö‘¿ì²]�&l�„6HÂ$a„ó„„^’6 ®ÜÔ½h4@£h~¨?"êÆëpQíˆè›”V�0ç3î´YåfžûIwõ™sK{?’]°õ¸¡¢î™3¿šb OûCwöq¹s&îçÎêy³»ñûʾÑ7±¢'*–bEÔ>±·øŠeûÄÞb.Väí{‹ÃXÑ‹|Z-bEm,TïS(Ž'{>ä6MþŸ-ÅÆÉp÷Oþ½–bÝä÷¹;Žþ�–c`ÒÛ[Ž'þª»~⯴ó&ä®`Kqéøýݬñû5çõóM3Çý¼ë?á]®ÿ¤w»þ)ïs7ôâžõ)·ðœ¿rKú¾êV^öÏnÕ¼#ݪ«Ç¹Vö•ã‘{ç»G¼9‹'7.w;·m(Ç«¯¾œ� ÚOs1LÊý½Ÿ8ïKnå#7F‹KÍ·+Fó“6ûÈ_ºÙGØ #ß #V€+Âm'F>ÂKÕ†ÎUFxŪ0tUœØgGWΉ}Öò“ZûÐäâ±Ï²µ«Æˆ^ïpLìJ£@užu§bý�×!š:ﲕù&]þ7í§ÿÛ]ôãO¸ëÎø¨[þƒ¿ÈNSðâöÿÂ=söî¹þ“Üž—º—·Üë^{ñùl;THœ?qÿŠâ¤ïè7e…F3^zaWE1ÒLìxr]¹¨i6¶ ÞTQ 5«o8¾åX|ù7ÝÍ—~½n,ºhŒ[ôÓ¿­ˆkÏù´»úŒ¿¬Œéæ®þáŸÖ�˧¼Û]|ü¯×�ã~Í]|ì¯VÄù“ÞTñ»&lnjȲvFßäƒÜì“ßåfïÝî’SßçæŸó©r,¼èˆr,½êÛ¹æ M  ’ššO騹ŸsS®ÿF6q³á|a„Wµ #œ§#Œð’°a„—‡ #¼üuáé0apjL÷Eì÷¤ˆý^j�Å> ádßa„—üC¹ûlªÁû,‡ƒë~Ã5±+�`”Ò £?¢ÄtŽ¡.o¨Á1x�É—ý?î̾O»Kf|Â]úÐ<›ssl?ùcîÙs¿áž¿æ4÷Âê«Ü+�ß¿ÏD†j <µi•{ðÎKܬ3Z@üx\dAÄE_Ï[²†M Œì_w—ð4™0ÂSf¯Β�X±«ˆÇ �’+¨u G¾øVè”�Ø~$1ŒQebW5Û�F`ÜÎ=Û³s2uù*uÕõ Õ둟<ûÐlþM¶¨‰מúgîñãÿ¤¢¡�5¾÷ ·ó‚o¹ç¯Ÿž�ê ÓBÏn]ŸíX¯Y2Ý \ñ­l‡ü¢Þ݉ÏG§¿ %º3f�ôÎr1×lÌ=ã#Å_3qÓ…_)ŠÍÆ­ýß©(,›‰»ožVQˆ6jÔù¢µÙxüá�Š"·ÙÈÓ\ ç�¯œ£As6Üyáß»íw]í¿ùL·qÁ÷ÜÃWŽs]þ_nÍ�¾èÖœóyw÷?éVœö'né ¿ç–ûnwó¤_¯8ý£ÚéŽûÅŠç²45Ð Ø¿F»…“£†¡I2cÍ’pÒÔ0 TÃ'S C“|† š4Ö(Ñ$¢±ýc ¡#—Û�F`„:×:ìI‡îiðÔ¡wG_ý¢ƒI­wåaåùtÕ?ÂS¥ùò±cÚanç¬ÿÌæ$xñ¾EîÕgŸÈ¶gÏs[³�XíØÞví$wCßánδº™ãßÝ�.¡�G7¡Ôl¸dÒ�îÜq?ï.žp€{àšcK·4G§Ueó®<~6¯Šš˜š7eÏ’ó³yQt V6çÉß*ÏiòØÔO•çãxhòÁUç×X6é·Êó_ÐÔ š¨†ýk ’æ1‹5J±F‰B—k�5Kty×X³$¼„náåtÃ/­Æ‘Wý¿Ñý�Õn4€„øùt^¤ÍŸ0máØ–E›tå_»Ì>,»¤.©ù§ÔžDð™é_È.k¨û—î_â^ÙµÕm{ü·áž¹Yñ«¹T¸_xÜÛ¢;®õâücöîÌÿ÷!ÙN좋ÿÞ�wô+oŸ|�{èî9¥w@JFòoòk/¿X19¬š¢Y“âÖ‹³&…NéÊš?ý÷¬I¡±.›X52V‹-=loScÂnÖÄ7ºY“p?�h{šiaÿH—¾”ŒMìÚ‰9Wh4]Hó'èp1u6u¸—& Ò¹Sá ÐhôÌý‚;}ΗÜì¾Ïfó'¬þþ_¸M¹ùb¡+?ìºbr¶#þÌÚëÝ–û®u÷Þú#·|þ8·à¼Ï¹Ë¦Ýal$´#©uè[}­S'>¿óñÒ«�v/›ú~7sÂ~î’SÞK“HXÊ“³&ŶMW¨Ñ¶Y“âºiY“b×ì#‡š{ÇN5)4ÙmllmGÔmjL~�»óä¹åSþÐÝ:õOÜ­½é–üð£î&]Ýã¿ÿÒ-<ó“nÞ÷?è®îý㡉\O{yòÌFOcK5hjÇþ5�¶ášØ•F0‚tNšŸòó'èð§–çO˜ÿwnFÿ?¸9—ü­ûÙÙÿÛ­9íÏÝ£'ì;B>´3ü쌯¹g¯:Î=výinðæé¦¸E³ÿ>;º@;e±�µz¡Çéèí°Ýù³S³£tôÃ+/ï)½úÆ‘Ë@úFkët25*²&Ń+†.“»úª¡&Å�g 5).?Ô¤8ûˆ¡&ÅÉ‹Ž×ÃÇ’mƒŽêÐ6=qÞ?¹'fÿ—{rÎD÷hÿd·a^O6¯ÆÆŸýÐÝwíñnÝ©îþ[†þnø¢\ i_°+Âb^§ÐùBŸ¦FZM mƒö ΟüVwEŸ¤G�´ï¿uÊ'Ü3çü]ös'Ðh†�ÎÑòó'è+M2ÓÊü š¨å´_wç-ø77ïêo¹e?ãî›ö¿ªÎŸ�í¼>1ãoÝú‹¾éîºä›nÙÅÿä®ûÉg³� ØNR½Ð| :²Aó/hÇrÝŠ™ÙyüšŸ¡�Èe }äqó^{~GÖ¤xyÓš¡S>îº6kT¨I¡yq²&Å“³†À³?ùÇ¡&Å©ŸŽŽÿÚ·GÛ¡mÒ|ÚFÍŸ¡mÖiwÚ~ͯ¡×£ù6ôúüe�Ÿ{fs¹ˆ~úÑ;ËvþrÁj^ÓÔ¨ŒN75¶¬_èÎ?ú€Êçœ|PöéÐØ»ý¤�VŒÛÛ§ü¯ì”èv£Ñ´‰&‚ÑõkuøÑÂû/Íf²muþ„‰ów½?ûw7kI�»þ¦cÜʹÿåüñW>÷Éž¹O:ÄÝsú¡îÖ³>ãn<ëÓnÎÔ?Èæ@w ͹0ï¬�gWˆÐ\ Ú±Øñäºaû6…\ÒG¯×öìjRl¹w¨I±æú¡S>ò“gªIqî7²æÀŽ©‡Fÿ¦ Wèùµjšh»ÔDÑ6f�•[f5)ö¾½5_²&Åó;J¯¸945š‹ŸD–)fNøÅì5ëõ‡ï‡B§H†ï—Bûa£C¡÷8|σ«/*ÿN|T›�H�?ò͇Ÿô¸¥SöÂИ˜��¥ð§ñùðMè0t¤ÚöâµÄ3§^Úšö¡Ñ4é…—wgó'h¶ØùkÏ/ÏŸ £ bMƒZ1yþW³™a/_Õë~¶|úÞ"þûnËã³s|cƒ@>6Mþ·ö˜÷¸ÛN<Ø-:í�ÜÕ§ì~Úó+Ñ��zqîÄý³]IAW‰Ðù§6íÝ9ÝÝÚN\;‘Ë@úÈãtT�4€*4ÂCO¯u˾ÆÍ½{F6‚. kÔ =N�×z–Þ?'»ÄàÓ7Ÿ��—«´h—≞?rë'ÿ~6Á×âI¿á®éy»»¬çmîÜñ­]ZM—‚¼fÆ¡Ùá“k–Lw›Ö-Èþv3rHyÑZ„§ºø G…ø&faa¯ð—..ÇÞü ‹{EX؇ã‰*Y#9Ƥ;~øùÒo©}ê6úúúܘ1cÊ¡Ÿ½±cÇ–— ”–v~y£Ø©A#vìÞêÖ=±Êݲþªlþ„3n92;Ò Ö0¨:¢A׬Õ:ÒáŽÍ·¸��,u;×^Ÿ�K«A°Zsë±v'ÀÝsÌ{ÜòIïp7NüUwå„Ý~)úª^è MÔ¤ó2õ(klìÝáxé…]¥W�rHyŒFD'Ï,*üUTtåä™§i¨ÈÙû·^Û¨K¦j›u妬Iqÿ’ì5©£×§†LJôí}ßä7WîkL>È­]vÎ>E«ŠÙ|�«¢7_‡Í*¢ó…µŠí|Õá£ÕÉ«‰x\|ì¯VFÏ[ÝU'þ¶ë?éÝCq»\ÿq¿én8ù}îÆ“?‹Ž}g‹�¯[zÂï¹¥“ßU¾œïÊ£ßUq©_…ŽÊõWÍññðÞýaUúÒ-š‰GØQø#²ÊñÓ¯h¨(†K6Ƭ¸´¢1ãO/ #lì¼x{¿Û>¥rÌÜ>åãYƒ§Ýj4„ÍOËz{{³ÿ«  ¦€tzy3Ø©�§ùžÜ¹É­ytY6¬US]ï¢ÿÈæBˆ5 j…£¹4ƒÖ¥947ÃKO<�%·v„tؔ㠓XñXϺö¢luýô«'ü²›=~7ó¨Ÿ�òõB>]7ó°ì2‘k—žYõ2‘©#—�ô‘Çè4� ¡i�¡�k¿ã�í�ûÉ3/Ÿ”íÀk‡^;ö±¿ÕþÀÈŠ ?yæÕß* –^8T8Ü·({=ùÉ3‡›š ©]rZ§-ä!:½!ßÑkË7BtºD¾¢Ó*ò��~‘o„è4�Ù'ÿöÐec}œôŽè¾ÑZœ?îܬñûUÄ¥{÷©¯`Eh_ûú‰¿RŽz~Ë-<îw†!{cÙÉ�]xÅ÷ÿÌÝýÃOf±æG_Ìâž_rõý“{ø’wÏ92‹�WŒs[oèuO/™YaaŸåë“ë+ üVç“é„Gn>ÝÍ9æ-î‚qop—M:È=²ð¥[Ú«åFƒ 5<ý<88ØñåÍ`§fôyù•Ý–gÌŽ&¸îÞ \ߊ)ÙQ­ÌŸ «Bèê:ÊAG;誺z„¾…Ð{�ï©o,4‰VømÊÖc?”ucï>æwݲI¿• j—�S6 ÆÊzá/©?dêþë{«—‰L¹ ¤�tš‹¶3 ÿ»õáOë)G©)†Ÿ�ćoT•cÚaÑßQ#‘ÿö^¡/¥òßôç�Ph?Ó1àcáÄ·UÖ Ûù\Ey¾Pou_•ˆGþ¨ÅHÏ¢Û‡ë 2MŸ:á‹ÿ|áßÓÓ“ÝÖéåÍ`§Æ®Ý/ír·­ËæO˜·f¦›¹ì¸–çO8öÚ#Êó',ypž[ÿÔÝÙü ¢o 4³µþi†n¦éÏñÚÒóA·nòûÜÊ£ßéMüu7oÂAÙ &n£‘¿L¤’N\&2Uä2�>òVi_!+jK3ÅûsÌ÷™<³4I\Ñ´¡Ë fMŠÜä™*²µÍY“â®k³×£#DTœo?ñ/+×á/‰Wš—# TI¹¸/M(ZŽÕWU÷ÁûäCseT÷¾ÁD£Wã•Q:­' E~>’r”.EÆÎ¥T|s¿mù,÷äêKÝ#3²Ø´l¦Û|×nãÊ Ýý·LŠ3ÛsTH¤P�íG�Gµ+Èèh¥vkj2Èþþþ¬ �n(T[Þ vjÒ·sÏölþ5®¼ëìì j ÄõBW†Ðü jLܶñÆìʺ‚„§.¸ÎÕŽ�. £I¡t™È‡&ìî:æÝY—øº ou—�£;oÜÿˆ&i½È_&rÃ=s‡õ2‘©"—�ô‘Ç@DµÉ3o½8+òtä¤ À‘Ÿ<óO"ËŽ,Ý¡&RXÜëKª°¸/Ÿ"„oðøðs{øðMŸ0ü|>Š­’ºü·÷ æ i.~2þ ¥w³}šj4ˆŽj5ÂÂß7:½¼Ú©!ÒˆÛÖ,q7¬¾Ò]¶ì7cÑqî{×ý³›Øÿ¥hàV5÷snʵÿŸ;káÑnÖÀ»«.sË×Üâî[·¶âùÖ¯Yí6üì·éŠSÝã?ù·ñÔOº{�y¯[qô;܉¿ææŽ³»p\kWu˜9á—ܬ)¿ç®:ë³nÁEÿâ_}ª[µä2wïšUÛ@AQ$¼c©[¿z±{xÉ\·áæËÝÆë~ì™–Û<ç{nË%ǹÇ.øn¶ŸóÄY_sOþà î©Ó>ã¶�ðçÑ9åxúä�g¯­Óþ&{ÍaZqCöžûXç­Ñß A„¡àîÕ‹*âŽ׸Ûn¹¨"n½qFV7„±hî î†Ùß®Õó~òåŠèŸñwÙþ¼".�ö§îÂÞQ3'ÄëÍ»ÒnMÑ # D5Æ&kìôòfè‹îáçOÐä‰ î»(›?A“*Žëÿ|´iP+üü ³WÿÀ-œ“ÍŸ Éct~žÙ{vÁ܆ÿ­»óø?È.yí„·dç§Í<ªµ£tíêð2‘:·éÙ­ëKÏŠv"—�ô‘Ç@w‰NžY:¥áùÏȾi÷“gn;©ò´‰rWyx¾Ÿ/‡®Rñí}i¢Ë0ôœá·÷þ >t køÍ½"µ«vÝBõJì 2�˜Üµn£A�…pކP¸<<Ú ÓËÅNÍÈÐü :-A§'è4�® Ób ƒz¡Ó$tº„N›ÐéòH—Š]/l2tê’x:K͆ḂLÓ§N¤„�šÎÒ„‰š8ÑÏŸ  [�?aÊ ßÈ&tTcbù†ÙD�jXT³û™GÝ£«/vw_ú¯nÉéŸp×ÿ.7{Â[¾L¤.9tÝO>[q™ÈçžÙ\z6Œ4rHy ¤M͆gN?<;õbÇ?O“H\§ÿ.Óh@]º¤£NMÐ) ºÔ£NYЩ ±†A­Ð%&u©I�2¡KOê”:•B§TÄèò�º<˃·Ïr·_ùŸnᙟtWžø.w~‹Wv8ÒÝ•§þ¾ûÙ_µ—‰L¹ ¤�<l —h4À@ظW_{Å=öì†lþ„…÷_ê.¼í”–çO˜8ïp×»è?²ù´®5�.«:‚<¿óñìIpëcÝug~ÜÍ>þ7¢Í‚Fâ’É¿â®ùþ¹¥ü­[»hj6묞é"—�ô‘Ç€ ä2`�†÷¥Ë9jþ„U�üÌÍ_{~yþmkÔŠÉó¿šÍŸ £nYUvÊ»·–ž©’æ4xúÑ;³94×�æ<¸ê‡ÎŽ2ˆ5 ê…æ\¸â˜_q Oû#w{ßîÁ›¦ºí�¬ä2‘F‘Ë@úÈcÀr°�FC£y Ôü =½Ö-{ø7÷îÙü '-øÇhà^èqz¼Ö£õiþ­?F×­Ý´nAv]�AWe˜uÒ;£Í‚z¡«Aèªó'¾Å-9ùnÍ�¾è6_s¢{þ�%/=#Fvj€ô‘Ç€ ä2`�†FÃ@¨#t$�Ž(БgÜrdv¤A¬aP+üü :ÂAG:hþù # ò^ܽ#»òÂàê‹Üm×Nr7]ø7gÚݹ÷�6 êÅ…ã~ÑÍÿf·pâÛÜŠÉ¿ãÖõ~Ê=1g¼{aå÷ò–{Ýk/ÇçpÀèÁN �>ò°�\l ÑP€•�Pó'hŽÍu 9f­ššÍ� ¹bMƒZ¡ÇhîÍÁ uiNÍÍ çÈÛñäºìò'wß<Í \ñ-7ï¬�»‹Nx{´YP/Î÷?Üeãßè®�ð·dÒÛÝ]ǼÛ=xâ»íç~Ý=Ý4÷â]׺Wž\Ïu‘ÅN �>ò°�\l ÑP@j¡®¾ «0èh]•AWgÐQ­ÌŸ «Bèê~þ]5BW�ÈÛóÜV÷äÆånÝŠ™nùüqÃÝeSv3Ç¿!Ú0¨³ÆïçæM8È-œøknåÑït÷ó^·¥çƒnû©Ÿv;gý§{þÆ3²Ë!½²­úä�@;5@úÈcÀr°�FCÝ:î~iW6Ï�æ;˜·f¦›¹ì¸–çO8öÚ#Êó',ypž[ÿÔÝûÌŸà/¹áž¹Ùeo¾ôënîq}=o‰6 êÅùã~Á]>þMîú‰¿â–NúMw÷1¿ëš|°Ûzì‡Ü¶cÿØí˜v˜Û5ûH·û–™Ù5—_}vßÐ vj€ô‘Ç€ ä2`�†Fz ܹg{6‚WÞuvv…5b ƒz¡+Chþ5&nÛxctþ™È{oý‘»µÿ;nÁyŸs—œú¾h³ ‘˜=~wõ„_v7Oúu·òèw¹û'ÿž{´çƒY3ÁÇ3g~Õíºt¼Û³ôB÷Òƒ+Ük{v–¶hvj€ô‘Ç€ ä2`�††k Ôü :5aÑà7{õ²ùtêB¬aP+Æõ¾<‚û.*ÏŸ S*<™È‡îžãn¿éä¡ËDþ÷!îücŒ6 ê….9güîÆ‰¿ê–Oz‡»ç˜÷¸‡'Àm=öà …mÇâž9û÷\ÿInÏ­»—7ÞÉ•0lØ©ÒG6�Ë€ 4 hç›ççOPñ¯&€æOPS@Í�XÓ VøùÔ”PsBM 5+Bþ2‘k—ž™]&òº™‡»Lä„Üü ¿ìOú ·úèßvë'ÿ¾{¬ç+› ¥Ø~â_¸gg|Í=Íiî…ÕWqåŒ8vj€ô‘Ç€ ä2`�†Zyó4‚NKÐé :MA§+è´…Xà^è4 �.¡Ó&túÄOÞ‘�Ná…—‰\µ '»Lä½nù2‘?=úÍnnÏo¸…Gÿº[qô;ÜÚcÞã6Nþ@´™àcû÷>áv^ð­Ê+?]†� }ä1`¹ Ø@£¡š±wѷݤþ/»i ÿ-û9O&j¹Ÿ?A*¶:”¾‘Mè¨ÆÄò ²‰Õ°ðò—‰œΧZ¾Lä¹ös—Ÿô;nÁ)à–žü?Ý]“ßãœü~÷DÏE aø+?ì^x6W~@RØ©ÒG6�Ë€ 4š¤æÁ„y_¬hL˜÷7ûöÞìR�:e¡•ùt‰I]jR§LèÒ“º¥N¥ðó'Ä.9gÚ[¿L䉿å®=ý#nñÙ‡ºÛÏø+·î´�f—‰Œ5b±£÷o*¯ü°ëél;�±S¤�<l —h44IG2Äß¹ò°È²}câ¼Ã³É5ÂÂû/uk]Vž?áÕW_®¸LäâË¿™]&òÂãÞmÔ Mਉo:ÿp·röÿuë.ù–ÛtÞ?¸§§}&Ú<¨Ù•.ŸÄ•`;5@úÈcÀr°�FC“&Ïÿj´��ÝOó'è(‡[Ö_•]†rÇî­Ù:t™ÈG¼¹â2‘—M=8Ú,h$t‰I­Cëºç¦SÜ# àž¾ú¤ì4�ÎkD#¼òÊK¹òF vj€ô‘Ç€ ä2`�†&iN†cúþÌ�~üAîœIû¹Ó�{sö³N—Xöð5Ùü šŸá•—÷D/Ù×ó–h³ ^è¨Ýpó¥_ÏŽvذæJ·õÞkÝž;¯Î&Z|öÜod/F‘Ø~òÇ*®üðÊã÷såŒZìÔé#�ÈeÀ MZ¶ò7cÂ/T4ôsÿ_,~™ÈñoÈæ]Ðü š‡Aó1h^†ÝÏ<š]RG¨1 �.k Ä¢|å‡ë§så ‚� }ä1`¹ Ø@£¡IWMÿÓh“àÇã"˪„®¡+Cè ºR„®¡+GˆNUÐ) {n½Øíºbrv*ƒNiˆ5bQqå‡ûqå ìÔé#�ÈeÀ Mª61ãŒÜÏçNÜß]ÑûawÓ…_q·];É ®¾È=µi•{q÷ŽÒšœ{íùî¥ÁeÙ$‹».ïž™þ…hó Zèþá•´>Íc§Hy Ø@.6ÐhhÒ½ªh(øè›üËnÍ’énÓºnç¶ ¥{¿îÕgŸÈŽ0ؽhFÖØ1í°hó ZdW~¸b2W~:€� }ä1`¹ Ø@£¡I:Í¡ïè7U4Î?ú€lÒGO§+¼¸ö¦lN„�?ý÷bW~Ø´†+?ÆN �>ò°�\l ÑФ—^åî=önöø_rçõóîâq¿äÖûûn×ÅߺòÃÉ‹7"‘]ùá'ÿXqå÷ê+¥g0\Ø©ÒG6�Ë€ 4šôì�¾ml;öÑe¯GÅ•Ö\Ï•€.ÂN �>ò°�\l ÑФFNƒØ1õЊ+?¼ºãÑÒ£t#vj€ô‘Ç€ ä2`�†&=sößE› Û¿÷I®ü$Š� }ä1`¹ Ø@£¡Ij&lŸòñÊ&ÃÞŸ5ù#€4±S¤�<l —h4´@͆gN?Üm;áÏÝŽ~ž&�8vj€ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` ·ãfçn?Ľ²ä­Î­þðÐÏ’ÄßdÀr°�FC „@ÂÔT8À¹[öS>–äÜÓóKw�þ&6�Ë€ 4 ` DR^ÙåÜž õãùuCEx½Ø~“s�÷5Žo,ø¦s÷½~¬9̹»>U?V}йïŽÇ’ý*› >–üâë÷Ñãcë½çsñíüVüu=rjü}yò’ø{ûÌ@üwóÂæÒ/@“ÈeÀ ­P!ÀáÖË"¹z,}K¼`&FWÄ>ŠØgKûl*6ôìûÙV<63žÛÄsL¹ËÑW÷”’h/ŠÀr°�FC³´ÝìáÖÉÕƒ"™ Ų·ÅLjÛ‰�1k��Q~'>Æmž#Ÿšgw®Š�Ó/>^ØÑm(NÈeÀ ͺýOã;ÉÑM1p`¼hËÇʃãE\>î>4^ÔÅ"VäÅ¢Ú7äù¨ö�y>ž»'^æãåC¹¬æàÒ7çÞ·ƒö>çñÇiý±çÕzbÛ½åÌøë~xRü}»ïˆø{çÇã¿»åï¬ÜvbdcÉþñßSµ«–SjúÆ>7ŠØçLû\*bŸcÅk/å€z­e˜A£°¡k }}}n̘1n`` ´Ä¹±cÇfˆ{yMúÖ-¶“I´/(’«‡/’Ñj¬|¿{mñ~ÎÝöÞ¡o·S§"2öÙQÄ>{ŠØgW±ñäøç¿ÚPʵX*Wc9¬â<6#j^Å~Ow|$þ{Us,ö9P3-ö¹Qó-ö9SÆ>—»îŒŽÃqP÷cRWÀ € ]ÓhPÑßÓÓS.øÕxèííÍþ¯eº]:½¼®ÕªÜ¡ÉErõ HF—b§¦Ë¼´5>†èt†Ø¤Ql Óé±1P§WÄÆÐ{¿‡uúFl§ñܽNêš�j“¼æC§ Æ>'ù¨uJ>bŸÓXèÉØg=ÕæBɇNáÁßdÀ†®h4¨àW±6Tøûÿ‹~ìøòºªnmá›P`”b§m¥ /cÅ^µyxªÍ»£¦p¬XÕ„�±‚W+˜±‚[±ø •ψÑš7)ö9ÉÍt‚~œ˜1â�ûj0H¾Ñþþ¶N/oˆÅí�QŒF�£K©ÆŠ-]z5,Ò|èR­±bO—v�‹ë¿/8uéØXÁZm‚áÅ¿/˜ b¤‚fMõ¨Õ¬Ñz8 0eÄ aÁû�n(T[Þ ŠÀrHT½£ uÊ^¬àÉGµÉ^ó¡S cY>ª�‰XÑ‹js ä£Úœ(ùЩžáûFݵNƒŠE£§&Ç¢™Ó•cQíòÐ�Ä£?Š�%�ÄÖ¹ññª‘¨v…¥FB i FŸ­a8:iïQ� }?cýýýY# ,ü}c ÓË›¡â„ ‚ ˆ‘‹-wÏp/.ýÝì(×–þ¶{ìî3¢÷#Z‹õëV¹‡ï[T76Þ{�Û|ÏEucËšsÝãwžZ7ž¸sŠ{zõ·ŠgW~¹¡x~ùGÝó·þyÝxqé{ÜK隷.~S¼8&¢nÄrªÑØs뇣¹ÛHì\ñÙèøÐHìXõ÷Ñ1¨‘ØzûQѱ®Ñˆ�§�ÆÃ÷Þ·‰õ÷ßýÛP-ô|¯.~cåï»CG'í]s㣠4oCl²ÆN/o†ÞLé#—�ô‘ÇèZYS=8²† FG¬|i@lŸ½km\þô…ð(‡ðhƒN/o;5€ ä2�>òHX3“­W›C¦‘xvy¼yÓHè…XC§‘Ðå}c ž†¢ÆÀ�D¬Ôh4Ú0ŠM$" M>Ýf{×j;5€ ä2�>òH“­ÃÓÄ¡±FQ#Qí O�F¬QÔhl<9Ò(j0ªM’ÜH¬=<Þ(j$îøH¼QÔH,g¼© åt›í]«]ìÔ6�Ë@úÈcÀrHX3G'D£@×#—�ô‘Ç€ ä2�¸a::‰F€®G.é#�ÈeÀ†Nç2�]�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ ØÐ�†1cÆ”cpp°´Ô¹±cÇ–— ”–v~y£Èe }ä1`¹ Ø0â�†¾¾¾r‘¯ÿ÷ööîóÝ®¦€tzy3Èe }ä1`¹ Ø0â�†� 5D…¿ž~ÖÑ�^Þ BÀrHy Ø@.6tE£ÁŸ¾ÐÓÓSZ²oá¯ÛÔèôòf06�Ë@úÈcÀr°¡+ žŽfPÁ/©4‚ ‚ ‚ ‚¨ŒNjªÑ :²AÔ ßèôòftúÍ0<Èe }ä1`¹ Ø0â�Iàõ÷÷g¿h¾†Ød��^Þ BÀrHy Ø@.6Œx£AÍ?Gƒ?šÁ —‡Gtzy£Èe }ä1`¹ Ø0â�†”16�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#��´Ý¼~‡;¤÷v÷ÖIK܇§­Î~�žáÊe º¹ ¤�<Ò¥Bä€ îç¾sK9š´ÔÍ_ûtéR0œ¹L£@×#—�ô‘ÇHÉ®^q¶í©ëž|>Ûq¯7=°ÝõÝöxCqü‚ Å7/}À}}öýuã°kܧκ«n|pê*÷î“VDc¿qK* ‚ lÅûOYYýÚ‡F€®G.éR‘ÅáÖË"¹z¼åè¥Ñ�l‚ ˆÑo8rqé/HûÐhÐõÈe M*R›=D“"¹zP$#ß�,#"½¨’Ëï�r[é¯pûÐhÐõÈe û„ÅþÜ5[³B}Ú¢MåBüïg­sožDaLt8q ÚØÉÇÁ§¬Œ6Šòqè9wGO±È7±ªÅÌå�íÓ‹Å‚uÛöi¶ÅâžÇžÛ§y‹»_Îò]ÍÁ|>´÷}›s×SÙíÒ0œ¹L£@×#—�ÎØóò«Y1qç–]YñqÉOfÅÊÉ7lt=×mÈ ¡¯ôÝ›Oþ[÷ý9W»«‚"¹zø"í¡EçqïwÔâìÛOš @š†+—i4èzä2P› *VË7>›b­z"+îTä�›÷PV~næ=Y!ù¾½;*m§¶WÛ­í?å¦GÜ9ܰ„¿É€ 4 ` l —‘5 ÖoÝ�5 tˆ¢ ïüü*Ò?2ýެ`©IþôÜ:ÂAÛ¢#Ô0Ðá7ùz :R¢ÕoÙ9ܰ…¿É€ 4 ` l —1üü:Ô]Ŷ dÞáüG\x_V¤ë²�*ÚGbþ=§ž[s(h[4§‚¶MÛ¨mÕ6kî½ÍŠפ×6ÜÈcÀr°�FC „€ ä2Šð—K\µigVlÇæ/8ü¼µY‘®¹T´Ç úN‡Ÿ¯à�ÞÛ³mÑQ:úAÛyêÂG²†�®î × «=è5¥„<l —h4À@Ø@.C¶>÷RV\<ôÌ>ó|gîƒYÃà°k²"]ûHÍ_ð¶É˲çÿøwî3Áô[6gÛí'ÔëѼ £y Ø@.6Ðh(€�°�\¶eóŽªÎ_ð­Ë³Â\Í‚‘ž¿à�',ßgþ54´�?Zöh¶Ýz Eæ/MÈcÀr°�FC „€ är÷yùÕײâ:?�ñW!®¢00PZÚùå�b l —;'6�Šoá*ÆU”çç/Pñ+ê;ùù ÔÄжi;«Í_ fºy Ø@.6Œx£¡¯¯¯\äëÿ===åÿ÷ööfÿ×íj H§—7ƒ�°�\®/œ¿@Ŷ oÞ¯BEá›—>à¾ÒwoV´«€®É÷·${>�z¡ç>âÂû²†AÏuÜÉ7lÌŽ.Ðvj{ïyì¹lÛÕÐ^üMl —h4À@´G­æ�¾é÷§*¨¨�û-E•I÷¿$kèª z~mG~þ]íAÛ  {ð7°�\l ÑP!P݈4r¡9ü© jh”Á ×opoÊõpÐÞŸµ­ÒÃßdÀr°�FC „m4G€®F æÁ%w<™M<¨æ�NÐUt5ƒN74⧮ʚ~NƒS>’54�£ŸÏ :Báý§¬tûµØ½wÊm4€„ñ7°�\l ÑP!,h´yð†#G ÿvD­æ�®žpç–]½Ô"¹ ¤�<l —h4À@ˆn¥ËŽöæA3Èe }ä1`¹ Ø@£¡B '54 ¡šºìáô[6gW6PØŒ5î�ÞÛ³æA¬èoW¼ó„åÙÕ=çîìy'͸ͭ´7šA.é#�ÈeÀ 0Žùæ�Švï*âUÌ«¨×‘±¢¿]¡æ�šjVèyÕ¼PCGBh»td„Ž�@óÈe }ä1`¹ Ø@£¡´Ñ<€G.é#�ÈeÀ 0vMD¨«èªjè*ºÚÁp5t5tU]Ý�æAÈe }ä1`¹ Ø@£¡ÂáQ«y K'ꊺ”b¬ðoGÄšºT¤.©KGªy KIîØýri‹‘rHy Ø@.6Ðh(€�°u4ÐMÈe }ä1`¹ Ø@£¡ÂJ¶íq«6ít ÖmËš'ß°Ñ}gîƒîïg­ËšŸ²²£ÍƒýÇ-Éš?ãNwøykÝ7/} kühÙ£4P¹ ¤�<l —h40ÂFšo›¼,Zø·#j5æÜõTÖ<Ð6îzá•ÒÍc§Hy Ø@.6Ðh( Å�ðåW_£yä°S¤�<l —h4Ð-¡o,ßø¬›¿öé¬y ÂýÛW®wG\x_ÖR—‘Tó@÷b§Hy Ø@.6Ðhh’Žò°�\l ÑÐ5ÞÊJ·ßQ‹Ý{§ÜF“H;5@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@8²ܘ1c²èéé)-Òßß_¾MÑ××WºÅ¹ÁÁÁŠÛz{{K· ©õXý?üYôÜÚ–�Ö©õt#½&T"—�ô‘Ç€ ä2`�†[÷ê³O¸î˜ç^X}UöÿVŒ;¶\à«øWA|£ ¤ŸµÜ7'ü}EM¿žZ�• zÞ|ó$”} — ÈcÀr°�FC „­yñîëܶ“þÒm?é£CqòGÝ‹wÎ/ÝÚ¸°ÑRáŸ/òõ³–Çn Õz¬4ÓhP¨¨W„� oÓrý_ÿÆîë—)b¯UÛ>.üYá*þgÿ¼¨D.é#�ÈeÀ 0yqÍõnÇÔCݶcÿ¸£±ýÔO»=·^\zÖ×O�ðMO ˆðˆÑÏZ»-Të±¢b>lHµFƒß.=>,îÃÛ¤Ú}}¢–ØöªÙ ÇzZ·ožøF*‘Ë@úÈcÀr°�FC „Cž9ýKÑÆ@'bûÉ+=ëëTŒ«xöÅ~­fAx› p=®ÑÇJ3�_ÜKxŸümÕî›ßýßo¯ß=V?‡´®°Ñ�_Oþþ — ÈcÀr°�FC „Ct”Áöÿ"Úhkˆ{þúé¥g­¤âß×±Âßߺ-,ê%¼­ÇÊH6jQóÀoW¸½B£¡>rHy Ø@.6Ðh(€�°y¯îxÔm;é#û44OÃ+Û6•îÕäž k_hë_ÿ�¿§âZÅ»Bÿ ï° Pë±âïåö´=~ûü‘^¬Ñ»¯–å›Õh;ü:ò�-÷ϧõŶw´#—�ô‘Ç€ ä2`�†[óÂís³ÆB/ÜvYéÖÆù¢Yá‹l/¼Mö¾9F¨ÖcE?‡·ÇŽ:ð��Ø:b�Eþ¾¾éàÃ7;BÕn÷Ë´Žp=Ú¦|#ä2`y Ø@.6Ðh(€�°u:²aÏŠK³höHkò� ?rHy Ø@.6Ðh(€�í@£aä‘Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 0ެ¾¾>7f̘rè缞žž,<Ý'¼_~µî+º}`` ôÓ�ÞÞ^×ßß_ú©»è5¡>rHy Ø@.6Ðh(€�pd…�€ÁÁÁ}Šj-Sc`ìØ±Ùÿ%ß<Èÿ6&ò·I·5ô¼zþjh44†\ÒG6�Ë€ 4 ` ,à…ÍÎ=¾·ˆlæÐÿ[6ÔHPC!¤Û|!îùæAþgQq®õÅn«ÖhPèqŠð1ámZ®ÿëߨ}ý2Eþ9D¯!|\ø³Â7SüÏþyQ¹ ¤�<l —h4À@Ø¢'f9·äMÎ ðz<~A鯯…»",ÚÅÉ ¢½ÚQ ùŸÅ7jÝRAï×¯ç ‹ûð6©v_߀¨%<2ÃËÑ uû¦ŠoD >rHy Ø@.6Ðh(€�°äÉKœ»õíÎݲ÷×ÝÉXö6ç6O/=é¾Mâ¾ÈVAø¾HÏ?&ÿ³èqÍ6üóJxŸümÕî›o"ø&„B·‰›oh]a£!¿ �!—�ô‘Ç€ ä2`�†KVo t",=ií£ô¯/Ô}øÛý}$ÿ³è¾±¦„ W£¡ÿZ„FC{�Ë@úÈcÀr°�FC „%:Ê`ÉþñÆ@;cñœ{h\éI÷m¨ÈöE|¾àV1¯¢>ÿ˜üÏ*Úu?ÑcÂB=ÿ³>FÏÞ'ÖhˆÝ×o[#ük‘|£AËýói}4C.é#�ÈeÀ 0¶`Ïç–¼qß&‚æiؽ¾t§ÆøBÚ‡/¸ýyºÏôéÓ+ újëðT´‡·ÇŽ:ÐcÂõä ùŸùûú¦ƒ½†¼j·ûeZG¸mSì}À¾Èe }ä1`¹ Ø@£¡Â=vžËN�(ÇÎm9»t£mùƺ¹ ¤�<l —h4À@X€ŽlØræP4y$CÊh4t'rHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ ØÐ�?S~x5ÑŒù~ýp–ýN/o!`¹ ¤�<l —F¼Ñ _¿š a£Aÿ×Äyâï#�^Þ BÀrHy Ø@.6Œx£ÁSá¯ðTø«àégùÐéåÍ` l —�ô‘Ç€ ä2`CW7¿§§'k tzy3Èe }ä1`¹ Ø@£¡¤ÕFC Œv±¼ ‚ ‚ ¢sÑIœ: ë‘Ë@úÈcÀr°¡k š¨16Yc§—7ƒ�°�\ÒG6�Ë€ #Þhèïï/_bÒ‡. �6èôòF16�Ë@úÈcÀr°aÄ )c l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` YºZˆŸÌ³§§§´tH~’ÑðŠ&šø3¼Í_}Ä«õØüÕQDÏ­m©EÏ¡õ6Bë ïë·£Þã«=G~}ع ¤�<l —h4À@8²tIR_à«ø÷WQQ­Â<ä ußœ¯2¢ݯ§Öc¥ÑFƒî60ši4„´^¿žü:óZ}�Ë€ä1`¹ Ø@£¡ÂÖíØ½ÕݶñF·|Âìÿ­ ¡Ø7øúYËë}»_ë±ÒH£A÷Ws‡šj(ü²ð9ÂÛ´nß0Ð:ýýó»kþ9üvúõùÿk¹¿O¸Ü/Óûê…ë<å”S*nÓcu›ä2�>ò°�\l ÑPaknß´È�ïÿ‚›0ÏÇÝÊ�7•nmœ/Ä}ÀS1œ/Äõ³–Çn Õz¬4Òh�|®ÿûuè¾þÿ¢ÛÂ× Ÿ} \O½Â>\�¶Yï�„ëÓÿ«m‡—¿¸mº¿Âû¥Ž\ÒG6�Ë€ 4 ` rÇæ[ܱ×á¾såg;“çÕݲþªÒ³¾ÎCï‹ý°ö|³ ¼M´×èc¥H£!,Ê}@ò·…?‡ë ÿ¯mòÛî·-¿¿]áòü}üvè~~}Šj÷_¿¬ä2�>ò°�\l ÑPá�Snüf´1Љ˜8ïðÒ³VRñë‹ðZ…¿n g ï_¯iÐ-�†˜üzüv…Ëó÷ñÛ¡f… µîï›.~½V�Ë@úÈcÀr°�FC „Ct”Á¸þÏGíŒ#¯úk7oÍÌÒ³ÒžŠ^ßпþ[~OµŠc…þµ6 j=Vüã½üÏžŠó°Ïìácò·…?‡ëɯ3O·ù÷Äñ áúÂÿ‹¿�^³Oôÿj÷Ýk®¤Œ\ÒG6�Ë€ 4 ` lÞ¶çŸpã#M ÍÓ°u×£¥{5FMÊŠ°é ámаXöÍ�0Bµ+ú9¼=lZ„ÂÛ«ø’¿-üYÿêg¯Ösê~á¶ûu„ëË?—ß-ó�«×hðÏa ¹ ¤�<l —h4À@Øš®Ï : B¡ÿ/}èêÒ­èvj<„Í Èe }ä1`¹ Ø@£¡ÂÖéȆ%ÎË¢Ù#0²tă¥Ó&„\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` l —�ô‘Ç€ ä2`�†Èe }ä1`¹ Ø@£¡BÀrHy Ø@.6Ðh(€�°�\ÒG6�Ë€ 4 ` Y}}}n̘1åÐÏ¡žžž,Býýý�ñòëêíí�.Ÿ#¿®üó׺]ÿÖ탃ƒ¥Ÿâ´MZg#ôºý}ýó7òØjÏ®Ï"rHy Ø@.6Ðh(€�°u›w¼àún{ÜÍ\þXöÿVä‹õ�ŠvÇcÇŽ-ðYÁíéç°¡[W¸\ëñ�WÑ®KÂb¾Þíázµ�±"^ËüöI3�/|�’_g^+Ïa¹ ¤�<l —h4À@ØšY·?áÞ4~‰;`Â@9.XùxéÖÆ…Åzž–û¢Úε¾•¯¶®p¹ j Hl]úYË¥Þí~½Ú¾Øóê¾jLøÐsë¾ ¿Ì¯?\î×¥ŸO?ýôò}ýýß}&”Žp}áói¹¿O¸Ü/óï“„ë<å”S*nÓcuÛH#—�ô‘Ç€ ä2`�†‡\rÇ“îíÇÞê~î;·t4Þ6y™›~ËæÒ³ë¾°Uø‚Wü‘ úFß÷~YL~]z\l¹/¼cëÒϾˆ®w»_¯ß¶˜|®ÿûÇkûüÿµ<¿-Óãóë¨W؇ëÒöjůÏÿ?¶¡üýÃíÓýý{Þo$‘Ë@úÈcÀr°�FC „C>ee´1Љ8pâP@T¬ûÂ?¤"6V؆n^µuå—k*ŒcëòÏ!õn÷ëU!_m›òM�|Qkx~Y~áÏz^­Cá·+¿.½�þô ¿<¿ºŸ_Ÿ¢ÚýÃ÷Ô?v¤‘Ë@úÈcÀr°�FC „Ct”Áþã–DíŒ7¹Ø�›÷PéYk7‚W¡e¾hŽ©µ®p¹ÿ9¶.Ó¾ˆ¯w»_�–ù"?/¼¿T+ðóËÅ/˯#ÿs^~]Í4ô:ük®ußpñëíä2�>ò°�\l ÑPaó6lÛãÞ8~ߦ„æiX¿uwé^�ñÅzžŠØð(´*˜Uìú¢Xt_èV[W~¹Ö­õhY¾A uûB»‘Ûýzµ ±‚[Ï.×ÿ½S�½Wâ�x�ð9Âÿ‹¿Oø¾û÷Iò÷ÝkÆŒrHy Ø@.6Ðh(€�°5ç­xÌ8a ; B¡&ÃÙK·”nm\X¬{*\ó¾¨V¬ûëÿ>¼ür_lç—«höò·å‹éZ·ë6…§mŽÝþ±¾)+ðc…¼_¦·YÂuæé¾ávûõ†Ï‘>¿ZæW¯ÑàŸ£[�Ë@úÈcÀr°�FC „­Ó‘ g.Ù’E³G2 }j<ä #‰\ÒG6�Ë€ 4 ` ZS펑B.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@Ø@.é#�ÈeÀ 06�Ë@úÈcÀr°�FC „€ ä2�>ò°�\l ÑP!`¹ ¤�<l —h4À@8²ܘ1c²èéé)-Òßß_¾MÑ××WºÅ¹ÁÁÁŠÛz{{K· ©õXý?üYôÜÚ–n§×‚8rHy Ø@.6Ðh(€�pd�;¶\à«øWA|£ ¤ŸµÜ7'ü}E�¿žZ�•nn4hóM“Pþuáuä2�>ò°�\l ÑPaëž{f³{`eŸ[·bföÿV„�†� ßðô³–Çn Õz¬4ÚhÐcTØ+üýÕPøeú¿þÍßÏóËõÖþ¬ð�ÿ³^Ä‘Ë@úÈcÀr°�FC „­YÇ,wÞ¤7¹óŽ> ‹ó÷Æ«.(ÝÚ8ß*ž}ÀS"êç:÷6·fÉôÒ³¾N¹Šh_ì×j„·©×ã}¬4ÚhˆA e¾ð—üÏázòÛ¡ÿûmÕm±õç ùuäï�בË@úÈcÀr°�FC „C.›zp´1Љ8ÿ˜KÏZIÅ¿/²c…¿/ÂÃoù½ðþµ+�6<÷þþZGøÜùŸÃõÄ1áúÃí �#—�ô‘Ç€ ä2`�†‡è(ƒs'îm ´3fŽƒ[>\éY‡ sO¶/¸õ¯Šì�Šlñ ý?,ÀÿÖcÅ?ÞËÿœ§ÛývÆ þ6Ä‚§åþõÔ®?ßhÐrÿ|ZW­ííÈe }ä1`¹ Ø@£¡ÂæíܶÁ�7é�û44Oó[×—îÕ_<+|±í…·)Âß7ÂÕz¬èçðöØ‘áíz>‰5þ~ám¾ñ�_‡Wí6¿L�סהo àuä2�>ò°�\l ÑPakî¿íÁ@Ø@.é#�ÈeÀ %4€Ñ‹\ÒG6�Ë€ £ºÑPôԉ͛7go AAAAC¡Z¹“º¶Ñ I ‹N †W×6¤¯¯¯|yËf�fï« -4@ÛÐhmC£´ �Ð64@ÛÐhmC£´�©FCOO�3fL}}}¥¥CÆŽ›€î¤üT‡Âœîïï/-­\ž €‘ãóR188˜-Óßãpyø÷Yyí—ó7èáßXÅÀÀ@é–×s6\¦ÿûû’ÇÀÈSçs1ü[ÜÛÛ[Zê²ÿûåùúYÇó÷o†™FƒÞ€jo‚EÝÆàt' bÊÓ°i ?àù�ÉßOyîðÊWŸ‹ú¿ÏSý?¿ó"a^èÊÝØßUå±ö§ó·‡“õÿX¾Ú/Ö¾rX÷ú†�ç÷�µÜ×Ïþ>þKñõµ¿O³Ì4jí¬èÍÔ›¾áº‹¼pg%Ïçxþ~>¿t�0O«5´ã¢�!ÝE¹6òjÝ^-ß Ÿ|Ý«œ ›Õò4ܧVŽû¿Ó£ºÑ 7DEH~ôo� »……I^þ(åu>×tå¨ß‰Ñ¿±¿ÏÊépy«;2Ú+Ÿ›yµ Ú׿ï20²b�†°±k(oÃÇø¦�†Ü›é‹½a¾8¡Ñt·0_ó´££ö”Ë0uòè.ù�•�þ>ûÛòÅJ>ÏŒ<ù"#Ÿ»žþ.Ó0F^¾îm¤Ñ ûû¼ï?ê v\ý›«7ÈwcÃÐ}ª5Â�Oòƒ¥¿ðg#Gëý�õ·+wµãU+^ŒœØßæX®ú/ùŒ¼X£!lä÷¥•»ù¿Çaí¬h%¿Í4ò]˜|ç%ÿ†è.±�ål8ð‰r;Ìïüà`dø&ƒþ­&,F¿Õ�4( ?åhX�ˆr8l4(¯ÙǺG¾îU¾†cúYùœÏñ�ò;_W7ÊL£Á¿�ŠØ`G£èN¾ÀCù¬A/¿Ü7Âeù怑¡\ sS¡üÎ/éï²_.FN˜¯áßX±ÛÂe ö·�‘“ÏG¿ïæ¯o,è¶ð¾ám�Ðh4€¶¡ÑÚ†Fh  mh4€¶¡ÑÚ†Fh  mh4€¶¡ÑÚĹÿKI!-´ÒûÐIEND®B`‚sogou-srpc-ac65014/docs/images/benchmark3.png000066400000000000000000001460101525217424400210700ustar00rootroot00000000000000‰PNG  IHDR)ã 0XsRGB®ÎégAMA±� üa pHYsÃÃÇo¨dË�IDATx^ì½ |Tå�÷½**UjµË¶ìÖ¶¶ÚwÙJ[ºu+íº÷²]ïÖ¶¸µ{í�«ÏÍ>õví–BBI‚c©‚€¢�ˆ€å%!^!H „B’�7’ðærN8çÌ™ÉL2'™sýßÏçú�œsÍ™—ï\Ã\¿\/Fa" ÂD„ ˆ(Q6 ¢ l@DAØ€ˆ‚°a" ÂD„ ˆ(Q6 ¢ l@DAØäàÁƒ4vìXKIOO×ϯñì³ÏZ\N˜0A?#~ÎüÜùµ€÷AØ„ƒsç” Âïb¸Hž%&&êG#‡ñúšƒŒ‹/ÒóÏ?¯çùw¯b~ýRRRô£ÞÅü|.\¨à56€1‡ N!ÃÌ™3»ÎÛ wæ222ôš×9yò¤c§×¸ÍÊ•+ƒvÈøš|¿ækð_«¹³À×î¾6?ã/Ü\¸È�§Pn¯ü:Ï=²³³µ×Éè<¥;oìÆ\ß(ÆëÎ×u‚]›+;g÷�êw‡ùq8½7ê{Î8ÇÅ€«ùxO{¤±?®î wÌÍ·a/^GµçRAؤ»°ÁÜ T¸“f&”NwäìWþ½»ûë®ÃÀ×°w”ÍEJ‡Ãü:†J ÀÀ\¸3Î�R;¡¼Oì�ÿ`A—ž` #fÂ}Ï™�˜ßsü¾îkŒçÌ�ÁL¸a‡*D#Àƒ„6ðwƒÍ›7[:væŽd /ø|Ü|û0móHþÙ|ü3×ï®ÃÀuÌ×7:œÜAåöª`ö æ �™}òkgø¼}„ˆÓû„_{³.ÆíøúÆ1vmçù}È÷.üž4®iÀî{ΨË%Zõ1ñóé®®¹Ž mEµçRAؤ§acî r§Ì Ø|s'ÓÜ¡´å¯ÒN×àÒÓk¨€Ù[wØ;¡N#ó5;™`ïóHžŠÁ˜ësH ÌÁF(×ìÉ{ΨÏ%Zõ1Ù=;¬ízÕžHax�Þ„ æÛš¿Èû‚¨Ãcþ ³Ñ!í ækÛo ¸ž}H?ÿît{î”òy~ŒÜ¡5:Ò¼ð1ãöü»s8c¾v¨÷Ï!€qžæ×ßx<öεÙ[w˜;éöÁŒyä€9(b‚½OÌçŒ÷˜Ó±ÞÀA�ñZØ[ zòž3ês1ì¾CõË×áÇcQÂ�×ìÙ~MsásvøþÌuœ0×aGöÇÎ?ÛèPß—ÿ—ë猿êÚ;+½…¯gîô›‹ù/Éæœý9˜_ó9ãsGÌèHšÿbÏ?˜�Cöýþ×8Æ…ë÷Íÿš1{ëó5í�ÃN ºæû3Žóã2‡ìÄ Ÿôþâ×&Tø¹·³ßG ì¯e¨º�ÓñÞ¾¿ÌNÍÏ‘‹™@Çí„òœÍu80hJ‚ýºö÷¥ytŸ3c¾­ýœF]óý›_gûts»3߀·@ØÄÜéqú²¨3hsGÉÀÞùp*æÎ¤½~¤àNŽÓã7swÏÍ(æ€Â|Ü ósÇ×~�Èpïßü:u×q2_»;Œz\œ|š T·»çbþ‹³¾Ž¹Ãh”P:ŸŒy”�9à †ùµä*�nãt<Òï¯@ç·Ês6×±¿Ç� vÆü¬˜?¸�ð킽†æ:üXÌt÷Øx„ àA Œ/òæÎ{'Òüß\¸ÃÉé¶wÌt.Æ(�HÁ÷gQatHƒudÌÅüüÌÇaø¶ü|ŒßÍÿýÿp:Nækw‡Q�‹}.¼óóàböè¹ð_Çù}e,Áà뙇ÕÛÿ²ˆ@CñƒÑÓ÷œù6fœŽGúýè\ ãvìíÒ‰`ï±@çº{_†6£ø½bnC�Š�Ù??3Ý=6Þax�ž„ Œùv¡vJ‚á4½"’8=Þ@Ï-F}.�0ÝæŸÍáŒù¹…{ÿá¼®ækw‡¹n°‘üØ�zöé =y-�0?G.Ýa@ì�©;zòž3ês1ãt¼'¯‰Óu � tÜN(¯m°÷X sÁnÃt÷cÇÜ~x´ƒ92Žs1ö:w÷Øx„ àAºëú"Ï}4ŽÛÏõä ¾y=wCùKx8˜Ÿ§Ñ¡6¯'ÐÛΦ{Øü—Wóó ÷þÃy]ÍÞºÃ"pÜéµ·ÿµÙþ^ ô> ós edƒ9È1þ2*=yÏõ¹˜q:é÷W s�ŽÛ1¿¶�ê{�:ì6ŒÙ‘yªD ½—Ø�qœ‹A°×ÙìØé±ðÀƒô4l`Ì_äÍ�ˆî:NpGÂü—fîhšï�‡šsHì¯×\‡Ïóó0Mç©æk)µw²ÍSøöüœì�ߨÏ%æ€Á(æ)L¸÷Îëjö æÇË?› ߯ý¼óýÙß'N°'~~vÇæû ¥ƒn@B� aГ÷œQ—‹§ã‘~:g~|]Æéµ0¿ì×00×±?¶@ç‚݆1‡o\8|0‚>Ç핟ƒ�ydƒ r=ó{ƒ‹�ýuæº|}~}Íõ�o€°p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzFØ< <«ËžeÏêÇ2€gÀ³ ÜôŒ°xxV8–<ËžÕŽeÏ2€g¸éað4ð¬>p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzFØ< <«ËžeÏêÇ2€gÀ³ ÜôŒ°xxV8–<ËžÕŽeÏ2€g¸éað4ð¬>p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzFØ< <«ËžeÏêÇ2€gÀ³ ÜôŒ°xxV8–<ËžÕŽeÏ2€g¸éað4ð¬>p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzFØ< <«ËžeÏêÇ2€gÀ³ ÜôŒ°xxV8–<ËžÕŽeÏ2€g¸éað4ð¬>p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzFØ< <«ËžeÏêÇ2€gÀ³ ÜôŒ°xxV8–<ËžÕŽeÏ2€g¸éað4ð¬>p,x–<«ËžeÏ2pÓ3ÂàiàY}àXð,xV8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYð¬>p,x–<ËÀMÏ€§�gõ�cÀ³ àY}àXð,x–�›ž6OÏêÇ2€gÀ³úÀ± àYð,7=#lžžÕŽeÏ2€gõ�cÀ³ àYnzî6l;vlW9yò¤v,==Ýrœ7xþù绎gggëG#wp,x–<«ËžeÏ2pÓsÈÓ(õ#�aƒqÜ<ÍÁ ðmø\¤Ž‡‡ àY}àXð,xV8–<Ëžeà¦ç�G6pÀ` 222´p€‰¶°%pq‹�ÆG18aç�À ¡A¤Ž‡ƒ›/ˆàY}àXð,xV˜ÚíDFP{Ö]Dû‡wþ”mYð,7= Ì#Ì#Ìðq£¯ëà´°c¤Ž‡‡ àY}àXð,xV²o'ÚáûZi”]w]Z¯Wª�¶,x–�›žƒ† $˜×f0àpÁé8c>n�©ã¡‚Æ!xV8–<ËžåÀ÷¬AƒQ²�x†èL<Ñg³‰*Ò‰ª7Õå5—u4ë^mYð,7=ûþ'P4À³úÀ± àYð¬(9_tB);í¹G›‚A‡Gu†§cˆJgt†Uë:GNp8ÑV«ß!èoЖeÏ2pÓ³ïS^MÐ8dÏêÇ2€gÀ³‚\9J”u«s�àVáp‚×…8ôQáSDÅ/•$�O#º¸¦3œh*&j­Ð$ˆ4hË2€g¸éÙ÷‰­&h2€gõ�cÀ³ àY1xMž*ÁÀNSÀ%Ëw¼dQå*¢ò7;À“Ïu†ä #ʽÛz·Êî!Dû†vÞïñ1��ƒϹ׉.,ï '84i9«?1ÐhË2€g¸éÙ÷ ¬&h2€gõ�cÀ³ àY!xšƒ¹CŸ5�(÷¯èÚÎ[ˆöÞÛ9 T®VuŽDàN?O›àéœß9­‚CÉÀ#xÚ…ù~Ý(9ƒ;ï‹ï·`tçcàÇs6µó±ñã¬ÏëœÚq­M²@[–<ËÀMϾOT5Aã�<«ËžeÏ ÐÞÐ9:ÀÜ9çŽyC¾vºOócàŽþåìÎÑpøÁ Qr0päQ¢ƒu>.AÌ�Õ�£;ø¾òG}LÄ¢˜hË2€g¸éÙ÷ ©&h2€gõ�cÀ³ àÙãð4a`îhó_ÿMk#D¥cîàsGŸG#Ôlé Êæ•$vðóàÀ`×�ÖççFQ`QL´eÀ³ ÜôìûÄS4À³úÀ± àYðìax¯}`î4�ó›F Œcîèóh îôsÀë;ðT ~ÎÇžè 'öÞ×9åÂüš¸U8œˆ¢E1Ñ–eÏ2pÓ³ïÓKMÐ8dÏêÇ2€gÀ³Gá­y­þ™}t@¬cîä7vvúU^“ow`µgÝÕ~ðï@Yð™-7=û>�Ô�Cð¬>p,x–<{ µÀA7wbù/ùA:˜p"^\³|QömÖÛíº£sÝ  $hÏ2pÓ³ïSBMÐ8dÏêÇ2€gÀ³‡àÎ0¯'`îXò_æyzAàØ%¢mQLsáu.x1LÑQ—ÛgS9€û =ËÀMϾO5Aã�<«ËžeÏ�‡×ózæ%ÿµ›;¼ÝÇQB/ŠÉ£1ø=Ä�¯sÁÁOßà°$Ü© ß@{–�›ž}Ÿj‚Æ!xV8–<Ëž=ÿÕœ·r4wy8}ˆÀ±‡énQLûû¢·Å˜¶aLÙ0¦kðã°-< ú´g¸éÙ×ÒÕ�Cð¬>p,x–éúâ“ÆZ"˜ëô´ðbšù#;Øä¼P)�Òàµ.xäp´g¸éÙ×’Õ�Cð¬>p,x–p,x–p,x–p,x–1�dY½o&mʉ6åÅÒ¦Üÿ¤­»þ]›V¡M¯øô:òé—º¦^p9ÿ§A–©;n°¶_{áé<íþ½'�dêŨ¥¾è„‚èÁÍÿ›}ïR5ÁÀ³úÀ± àYð܇pGƒçŸ›;&{î!jÈ×+ô^±ÝÜKß3SëèKv|¥µÎt`4‡»Åüzðëc~½"õW~.ìÕ5Œí=y—‹Ïfw.4É NFj{O^“Ä4oïÉ»wðç/ ^0Öw»ö¬»:w÷èŲ úqósÛ÷.T|¡‘<«ËžeÏ}¯„Ïs§ãÐ#Û®Ï4p玃Žûž¾Í³ÎyÍ‚‰kÖU'è_ùmsåíëUðã4?nûzÆ{LÆöž<‰·Þä�dx+ÎHmïÉ×°oïY2�(ës¶zwtŽ˜Jâæç¶ïÝ£&øÏNð¬>p,x–<÷¼íž}%ý¢q�[^F{Ð0ÇjikÒϱÊôÕ‚r xt�RàÑ Á#5x‡·ÿ‘ÅÍÏmß;GMðŸ� àY}àXð,xv™óiD;\ïðϼ¥_„è.h`àXm0—ßcðt*^¿�G%p(Àë:„±½gÄ OáâÂ÷Ë�"Xðh ,x \8°àÒTÜZt4ëO¸…›ŸÛ>ûj‚ÿìdÏêÇ2€gÀ³Kð¨þÒnþrÏÕŒà<ëP‚ŽeÏŠÀùÆB¢êM�Á$�VȹËúY-…§|p`ÁÓ>8°8<Ê4Ê"¹³ðsàÀ‚ÃþüãQXDh ™ª¸Ùž}æÔ‚2€gõ�cÀ³ àÙxN7é6)ç/ãü;B„40p,xVî¨ïú¼õ3…«<ûZg¾fËõQF'Ÿ¶äN?¯ýÀAOãà` /GO„Rx´—1Ê"dçcå‘üØy ?^·‚Ÿ/ÚiŒ²àÏS.šŽm¸Ùž}¯ºšàCPð¬>p,x–ë—i^u¾[ÞÙ 'h`àXð¬88컟®í¼Å÷sod¶Ë5:ì¼® wà¹3Ï�z^k‚;ù<Ń;ý…Ou†\Œ` k õs®¿ �ãÇÅ uòãäDø±óú8Fc2eÏêÇ2€gÀs„(�aýÒÊ¡C$þòh¢'AÇ2€g(é™×©0:ìܹærayg§^ÛúÓ×Éç)Üéç)ð” #0öFCá©*ü¸xê ?VÝÆ��§¶ðsáÿ/ŒQ<†Ÿ/¯ÓÁ#Kx‡óµ\ØâÔwU5Á‡  àY}àXð,xî%<=‚¿üš¿ò—Lþ+_± ¼ûÀ•Ö:ýlpàXð,x/:É�E}^gž;éÜ¡×ÚÔGYp§Ÿ ¯«ÃAÀ¾¡�ŸÙ¼Ø¥ùs<šÊ¾ûõ'|WT4À³úÀ± àYðÜ xÏzæjþRÈ_^#¼ÊºSÐPß\£Ÿí8–<Ëž]†d,x›Oc”…1 �·åÀ‚wâÀ‚G,ðg>oÊ�EîÝÖÿ"UxÍ⻢š qÈžÕŽeÏ2€çÂótí«ºó¼ä¯ŒÞÛ ��cÀ³ àÙ#pÍ¡E]ng`Qµ®3°8÷zg`Ák@£,8°à’u«õÿ£ð‚ ÄwE5Aã�<«ËžeÏ=€>ã¿4_ùg¦a"40p,x–<+Œã§wD|ýßUÕ�Cð¬>p,x–<‡�Zàá³æ/‚¼š:ÿÕ*ÂD*h`àXð,xV7¶8µ�°xxV8–<ËžC„·wãÅÌACÞ°Îa²¦ bOÄ‚ŽeÏ2€g¸éað4ð¬>p,x–<‡À•£D{ï³ ¼0/$aN_* ¸ÌÑ 8–<Ëžeà¦g„ ÀÓÀ³úÀ± àYðÜ <¤5{�5hàŽ\À4Lßôt¯‚†«gò¨nÑ“T5óÇtyáÿÒ~êÏ2ø,{­IøsJ�¹™Þ›|§ö;P�¾hÏ€§�gõ�cÀ³ à9¥3¬!‡.ÌŸeìACÒ†1TY_¦Ÿ þ‚zbêßÒû±·ÑÒ˜ô^ìçèÄ´oÓÕYz  ð,ÇÜDœøg]e‰ï÷ÓŸ¾¬×*Àí¹fú©:é;]¥fæ�"Þž6OÏêÇ2€gÀ³<=âøkÐÀ{¨7äë"K¤ƒ¦øÕQ´xâ�~�“‚)ß´|‘Eñv9‘ð­ˆx®Jz�Êþ¶Ç…GOË¡)÷ÐÁÉ_ïQÙ3ù+”?¤Gegü—铸/ö¸¬�Ôã²2v ­ˆ½%äòöÄ,Ž�’sƒv~µïzöûÈœt‡ßcÞ7ØïuØÿ—ޝ­“+'÷UIÃßS(=)ßu8öºp,x–<ëœO#Ú9àzÈÀ?—¿©ŸŒ^4Ê/yoÎP¿@äí¸�–¶â•òVìý� €§�gõ�cÀ³ D{¾ZEtx”5hÈFÔ\¢Wˆ<*p¸é ¡½ª„*fÿX›ëkþ’º4é.J›t ­šu/: ŠÂ�Ã{sî‡g!àÿæÈÒ|¥Ê/¹Tžïˆ”]ç†ìzÝ/ Ù»!Þ/ Ùº|Œ_’¹`¤Cr‹åóÛ(Ü®# ÂàiàY}àXð,±ž¯%Ú{Ÿ5h(ݹ…K؃Ýp¾®÷ÁF[©ï‹ñôïk+Ü›¿ ÉJÕΣ-ËžeÏê¢�TJ°.î»$ᎈˆ€§�gõ�cÀ³ Dz¾´ž({�5h(IÖOºƒSÐÀë6ô–Ö‚-ôÙ´ïj«¿_NÓboÒæª -ËžeÏjÓ#•6OÏêÇ2€gˆó\:Ã2pèpÑÝaçn -û×RQâP¿ ÁþåmYð,x–�›ž6OÏêÇ2€gˆñÌÓ#Ž�± {î!jÈ×+¸ƒ[ACsÖ*âmÒb®o“Æ òÜb;hË2€gÀ³ ÜôÜmØ0vìØ®ròäIý(ÑóÏ?ßu<;;[?êþñPAã�<«Ëže ÂsËY¢ýíAáGˆZ+ô îàVÐиq.žò m¯x#hXžü%mË;'ЖeÏ2€g¸é9hØ�žžÞÕÑçŸSRRü~æó 0n4À³úÀ± àYÊ{¾ìûδ{ˆ5h(׹奋¸4t´SÃêXÚ?ùk¶ áËÚvr�@[–<Ëžeà¦ç aƒîüsÀpçŸCþ�G=¸}<Ð8dÏêÇ2€g(íù|ÑÎ×Cþ¹üMý¤{Ô7×ÐÌÍÏF4h¸ÖÚHõKŸ£=“¿Ò2pá½ä¯\>«×rmYð,x–�›ž» Œ© ‰‰‰úÿÎ?ŸãpÀíãá€Æ!xV8–<Ë@IϦ«»¾n öŒ¢âùŽõ#YÛOÓ>~º+høý‡ÿ“6å½çX7Ôr*o;U¾ô�´aÒ�– aí‚¥ãî?'”þ+n4làZ§Ÿáõœptûx8¸ù¢�èžÕŽeÏ2PÆó¥õ¾/(ƒ,A•$ë'ÝÅiDásYúÙžÑVšO§?Dëb?o ¶.Cá-n‰¶,x–<ËÀMÏAÃŒ5ŒQ æãæQn4À³úÀ± àYJx.�a 8t¸¸F?é.n ­[¨búßÓ‡±ƒ,AÃîŒñz�ð@[–<Ëžeà¦ç aƒ—Aã�<«ËžeàiÏí DÇÇXƒ†=÷5äëÜÅ� ¡eÿZ*Ÿú ­Œh l™¡×´eÀ³ àYnzFØ< <«ËžeàYÏ-g‰ö· ‡!jí~w†HÐtµ!âACsÖ*Mx€VÄÞj Žd¥ê5zÚ² àYð,7=#lžžÕŽeÏ2ð¤çËÙD»‡Xƒ†¢q�[^ö-mM4Ç„ˆ �çÒ™„hYÌÍ]!CZì:}¸÷ÓAЖeÏ2€g¸éað4ð¬>p,x–�ç<ŸO#Ú9àzÈÀ?—¿©Ÿt{ÐÀ¥WACG;5¬Ž¥¢„oÑ☻‚†%SÑgÇÖë•zÚ² àYð,7=#lžžÕŽeÏ2ðŒgµPü‚u4CÎ`¢Úíz÷q v¯ÕφϵÖFª_ú�òM¿ ¡âÌõíÆ{ Ú² àYð,7=#lžžÕŽeÏ2ð„ç«UD‡GYƒ†¼aDÍ%z÷‰tÐÐÑp‰êÞzšOù¥M¼¡+hX>mUWÕkE´eÀ³ àYnzFØ< <«Ëžeõž¯ø:Þ{ï³ £;w¢è#"4Ô–Óåy�ÓžÉ_é ¸¬š}ÕVêµ"Ú² àYð,7=#lžžÕŽeÏ2ˆjÏ—Öe² %Éúɾ!ÒAC{U ÕÎEÙñC,AÃ{s†Rc½;;i -ËžeÏ2pÓ3ÂàiàY}àXð,ƒ¨õ\:Ã2pèp±÷;3„C¤ƒ†¶Ò|ª™ñ0m‹û’%hÈ\0’Z›jõZ‘mYð,x–�›ž6OÏêÇ2€gD�gžq|Œ5hØsQC¾^¡oè¸ÖN ³ã#4´l¡K3~H'Ýe >^4Š®¶¸;%mYð,x–�›ž6OÏêÇ2€gD•ç–³Dû‡[ƒ†C�øzêîL/ é{fF,hhÙ¿–*§~—ÖOú‚%hزì êèhÓk¹Ú² àYð,7=#lžžÕŽeÏ2ˆÏ—³‰v± Eã:·¼ìCœ‚†MÇ—ëgç9k U&>HƲ »Ö¾Ð'Aƒ¶,x–<ËÀMÏ€§�gõ�cÀ³ ¢Âóù4¢�®‡ üsù›úɾÃ)hXwx‘~6|7Î¥òÄaô^ìç,ACîú½F߀¶,x–<ËÀMÏ€§�gõ�cÀ³ úÕ3�Z(~Á:š!g0Qív½BßÑ ¡£�VÇRYÂßÒÊØ�– áHVª^©ï@[–<Ëžeà¦g„ ÀÓÀ³úÀ± àYýæùjÑáQÖ !oQs‰^¡ïˆdÐp­µ‘ê—>G¥ в˜›»B†´ØT´/]¯Õ· -ËžeÏ2pÓ3ÂàiàY}àXð,ƒ~ñ|å(ÑÞû¬ACÁèÎ�(ú˜H  —¨î­§©(á[~AÃéÃ}»m§´eÀ³ àYnzFØ< <«ËžeÐçž/­'Êd J’õ“}KDƒ†Úrº<ïq:‘ð-ZscWаdÊ ª8“­×êЖeÏ2€g¸éað4ð¬>p,x–AŸz.�a 8t¸ØñÿðÐ Ú«J¨vÎ(:<å”6ñ†® aù´!TYš«×ê?ЖeÏ2€g¸éað4ð¬>p,x–AŸxæéÇÇXƒ†=÷5äëú"4´•æSÍŒ‡ißä¯ú µ•…z­þmYð,x–�›ž6OÏêÇ2€g¸î¹å,ÑþáÖ áÐ#D­z…¾Ç4,Û;K?­[´ aÏä¯t… \Þ›3”®\ö=ï(mYð,x–�›ž6OÏêÇ2€g¸êùr6Ñî!Ö ¡h\ç–—ý„=hà5xí†piÙ¿–ª“Gж¸/Y‚†µ¯� Æúþ Rœ@[–<Ëžeà¦g„ ÀÓÀ³úÀ± àY®y>ŸF´sÀõ��.S?Ù?D*hhÎZBUIÃéÓ¸?· ™ FÒÕ–¾ßQ£;ЖeÏ2€g¸éað4ð¬>p,x–AÄ=ó¨…⬣rÕn×+ô‘ 7ÎÕ‚† “î´ [–=AímÍz­èmYð,x–�›ž6OÏêÇ2€gDÔóÕ*¢Ã£¬ACÞ0¢æ½Bÿ‘ ¡£�VÇRe⃴6v�%hضò)êèè¿©!Ý�¶,x–<ËÀMÏ€§�gõ�cÀ³ "æùÊQ¢½÷Yƒ†‚Ñ�;Qô#™GÒz4\km¤ú¥ÏQyâ0úÐ4ìί׊^ЖeÏ2€g¸éað4ð¬>p,x–AD<_ZO”=È4”$ë'û�Åk{4t4\¢º·žÖ‚†•±-ACþŸfëµ¢´eÀ³ àYnzFØ< <«ËžeÐkÏ¥3¬!‡×è'û{а8÷Åðƒ†Úrº<ïq*Mx€VÄÞÒ2¤Å #Y©z­èmYð,x–�›ž6OÏêÇ2€gôØ3O�8>Æ4칇¨!_¯Ð؃†ù;&PK[“~64Ú«J¨vÎ(:�0”–ÅÜl Nîÿ0%ЖeÏ2€g¸éað4ð¬>p,x–A�<·œ%Ú?Ü4z„¨µB¯ÐD"hh+ͧšSQ·hqÌ�]AÃ’)ƒ¨¬p“^Ë; -ËžeÏ2pÓ3ÂàiàY}àXð,ƒ°=_Î&Ú=Ä4�ëÜò²Ÿ‰DÐÐZ°E ŽNù¦_ÐPqÆ÷Ü=Ú² àYð,7=#lžžÕŽeÏ2Ëóù4¢�®‡ üsù›úÉþ%ACËþµT�<‚M¹‡Ò&ÞÐ4,Ÿ6„j+ õZÞmYð,x–�›ž6OÏêÇ2€g„ä™G-¿p=dà’3˜¨v»^¡‰DÐМµ„ª“¾C{&¥+dàòÞœ¡ž´eÀ³ àYnzFØ< <«ËžeЭç«UD‡GYƒ†¼a¾Þy‰^¡Ù[úi¯ƒ†Æ�sµ !+~ˆ_ÐÐXßÿëPô´eÀ³ àYnzFØ< <«ËžeÔó•£¾Þü}Ö ¡`tçNQÀ¡sY4aíO{4t´SÃêX-hØ÷%KÐ�¹`$µ6Õê½ Ú² àYð,7=#lžžÕŽeÏ2èùÒz¢ìAÖ ¡$Y?Ùÿ؃†¹[Ÿ+h¸ÖÚHõKŸ£ª¤á´aÒ�– acÚ£tµ%:•H€¶,x–<ËÀMÏ€§�gõ�cÀ³ =—ΰ† :\\£ŸììAìOÇQ}s�~¶{:.QÝ[OSeâƒôѤ/X‚†-Ëž ŽŽþßY#’ -ËžeÏ2pÓ3ÂàiàY}àXð,‹gžq|Œ5hØsQC¾^¡ÿéuÐP[N—ç=® Ʋ »3Æ+40hË2€gÀ³ ÜôŒ°xxV8–<Ë ËsËY¢ýíAáGˆZ£g�ÄÞ íU%T;g•'£Õ±-AÃÞ ñz-õ@[–<Ëžeà¦g„ ÀÓÀ³úÀ± àYqxÛÊ#¨=ë.¢½ß"ÚõEkÐP4®sËË(¡·AC[i>ÕÌx˜Êþ†VÚ‚†#Y©z-5A[–<Ëžeà¦g„ ÀÓÀ³úÀ± àYa8hȾÝ.ì4þ@Tþ¦^1:èmÐÐZ°E J e17w… i±èäþåz-uA[–<Ëžeà¦gßÿôj‚Æ!xV8–<+Ì�ïYƒ£ì¼©3ˆˆ"N_*èUÐв-U'� ¢„oÑÒ˜]AÃÛqéôáèYôÒMЖeÏ2€g¸éÙ÷¿½š qÈžÕŽeÏ “38pØEpЗ9ºÇACsÖªNú&ÜG‹cnì –LDg²õZꃶ,x–<ËÀMϾÿíÕ�Cð¬>p,xV^‡aלƽ÷ê•ú{Ð�´a U7^ÐÏvOãÆ¹ZÐpxÊ7(mâ ]AÃòiCèbYž^KhË2€gÀ³ Üôìûß^MÐ8dÏêÇ2€gá­-�>Ö,k4%û¢‹Ñ1­À)h¨¬/ÓÏvCG;5¬ŽÕ‚†}“¿ê4ÔVê倶,x–<ËÀMϾÿñÕ�Cð¬>p,xV òGZ†œ»èÚÎ[:G4(4\km¤ú¥ÏiACnüWºB.ïÍJ�õѳ�g_‚¶,x–<ËÀMÏ€§�gõ�cÀ³B´ú:Ùö áL¼v*š<÷&hèh¸Duo=­ Ûâ¾d ÖÍHlÐÀ -ËžeÏ2pÓ3ÂàiàY}àXð¬4ìj Φê'£Çs¯‚†Úrº<ïqªJNŸÄ}Ñ4d.IW[ôš2A[–<Ëžeà¦g„ ÀÓÀ³úÀ± àYšŠ­AÃÎDç^×Ov žK« {4´W•PíœQZаaÒ�– a˲'¨½­Y¯)´eÀ³ àYnzFØ< <«Ëž=Nc!Ñî!Ö Áa]†þöÌ¡‡ = ÚJó©fÆÃT™ø };È4l_ý ut´é5eƒ¶,x–<ËÀMÏ€§�gõ�cÀ³‡©Ï³ Ùƒ.ÙŸž{4´lÑ‚†òÄa´&övKа;c¼^ 0hË2€gÀ³ ÜôŒ°xxV8–<{”ËÙ�á‚9hàcè/Ïö �§Q𺠡в-U'�Ђ†•±-AÃáísõZÀmYð,x–�›ž» ÆŽÛUNž<©KOO·çß žþù®ãÙÙ׿pDêx¨ qÈžÕŽeϤf‹5hØugР�éϽ š³–h;N”&<@ïÄÜÜ2¤Å ‚]Öõ(@'hË2€gÀ³ Üô4làÁèèóω‰‰]?›>–’’¢ýÌ·ãÀ€‰Ôñp@ã�<«Ëž=O“àuŒ �§Qðº ÝÐמ{44nœ« §î§e¶ áôaçi"mY ð,x–�›žƒ† f¸Óß]ØÀ¡×3àßy4D¤Ž‡‡ àY}àXðì!.,· ¹w‡40}é¹ÇACG;5¬ŽÕ‚†¢„oÑ☻‚†%Sѹ“[ôŠÀ ´eÀ³ àYnz9là‘FÀÀÿSÌÓ졇|.RÇÃ�Cð¬>p,xögS¯‡ \x«ËÖ ýd÷ô•çž ×Z©~ésZÐpxÊ7,ACzâ�Tq&¼ï"A[–<Ëžeà¦ç�Â#p"##£ë\¤B…@ÇÃ�_4”È”‹&Y‚†Ö]ߤӅ9Žuû³(ØK“3Ÿè b×ý‚väor¬k.Ň÷Qåüׂ†ƒ“¿Nio0 ƒ)o÷×@AAAAAñbq‹nÃîôóè…`ç9$0‡Fh©ãáàæ‹¢xV8–ôi"mY ð,x–�›žƒ† "ë2…ƒûq3æãæ�"RÇC�Cð¬>p,xŽB8h8ö„5hàßùxq˳SÐpè\–~60m¥ùT3ãaªJNÛâ¾d 2Œ¤«-= U$ƒ¶,x–<ËÀMÏAÃ/ƒÆ!xV8–m]*Ï×k‚Hƒ¶,x–<ËÀMÏ€§�gõ�cÀsÂÓ$²^xECßt¾ÃõÜ“ ¡­4Ÿjf<¬ û&ßm VL¿›j+ õšÀ ЖeÏ2€g¸éað4ð¬>p,xî#8hàuÌA¯ÛÐG„ã¹'ACkÁ–® !'þ¯ºB.ïÍJ�õzMàhË2€gÀ³ ÜôŒ°xxV8–<÷ç^· ¼ÕeS±~²oÕsO‚†–ýk©:y„4l‹û’%hÈ\0AC�¶,x–<ËÀMÏ€§�gõ�cÀ³ËœM½2ACkßw¾CñÜq­�Ò÷Ì +hhÎZ¢… UIÃ铸/ú W[ôšÀmЖeÏ2€g¸éað4ð¬>p,xv‘3ñÖ !d¿ Lwž{44nœ« Ò†IwZ‚†-Ëž ŽŽ6½&è ЖeÏ2€g¸éað4ð¬>p,xv‰Sãýƒ†öþû+0ÏNAúËô³t´SÃêX-h¨L|�>Œd v¾?AC?€¶,x–<ËÀMÏ€§�gõ�cÀs„¹æëdŸxÆ4}¬_ƒ&�çpƒ†k­�T¿ô9-h(OFïÇÞf vgŒ×k‚¾mYð,x–�›ž6OÏêÇ2€çÂAñ'¬AÿÎÇû'Ïá  —¨î­§»‚†•±-AÃáísõš ?@[–<Ëžeà¦g„ ÀÓÀ³úÀ± à9BðÈ…#�Zƒ†¢qQ40vÏa µåtyÞãZÐPšð-‹¹¹+dH‹@Çv¿©×ýÚ² àYð,7=#lžžÕŽeÏ€ƒ^“Á4ðš Q„Ùs¸AC{U ÕÎ¥ Å í4œ>¼F¯ ú´eÀ³ àYnzFØ< <«Ëž{ ï.q`„5h(IÖOFfÏ+òæ„4´•æSÍŒ‡µ ¡(á[´8æÆ® aÉ”AT~j»^ô7hË2€gÀ³ ÜôŒ°xxV8–<÷ö µ gSõ“хᙃ…Pƒ†Ö‚-]AÃá)ß ´‰×ƒ†eSSÅ™l½&ˆЖeÏ2€g¸éað4ð¬>p,xî!�…Ö aç¢òè[· øâaJÙöÅgü%®ÿwKÐðþÁùz-Zö¯¥êäZÐppò×)mâ ]AÃòiC¨¶Ò÷üAT�¶,x–<ËÀMÏ€§�gõ�cÀsà a÷kÐp1úÖ-à aRæ/-ƒQxÍ^»Á‰æ¬%ZÈÀeÏä¯t… \Þ›3”ê«Kôš š@[–<Ëžeà¦g„ ÀÓÀ³úÀ± à9Lêr­ACö ¢ªuúÉè‚G48 q=0hhÜ8·+hØÿeKаfî0j¬¯Ðk‚hmYð,x–�›ž6OÏêÇ2€ç0¸œÝ.˜ƒ>¥LYÿ„cØ0aíOõ&:Ú©au¬2T% §­qa 2Œ¤«- ze� -ËžeÏ2pÓ3ÂàiàY}àXð"Õ›¬ACÎà¨ÊjŠ(fÝÏÆ™Ÿ<«×êäZk#Õ/}®+hØ0éNKа9}4‚€¶,x–<ËÀMÏ€§�gõ�cÀsðz ¼.ƒ4ð4 ^·!JÙ[ú)Ådü\ ~o â2§Cç²ôšD —¨î­§µ ¡2ñAÊœt‡%hزì êèhÓkƒhmYð,x–�›ž6OÏêÇ2€çn¨H· {î‰Ú �×aà&ÌáÂäõ¿¢7ý?4qÝcÚˆKÐP[N—ç=® ¿MƲ »3Æë5�@[–<Ëžeà¦g„ ÀÓÀ³úÀ± à9gS¯‡ \x«ËÖè\ ±¶©ŠR·ÿÎ4Ìß1�ê›k´óvÏíU%T;g”4”'£•±-AÃþÍÉzMàЖeÏ2€g¸éað4ð¬>p,xÀg³­ACÞ°¨ x‹Ë¤ c,ALJްì8aöÜVšO53Ö‚†Ò„hEì­– áHVª^x ´eÀ³ àYnzFØ< <«Ëž85Þ4ä�$j�η�\£í.a„ q™£)ï³?ég¯cxn-Øb –ÅÜÒ2¤Å Sù«´zÀ{ -ËžeÏ2pÓ3ÂàiàY}àXðlâZQñ Ö áÐ#Q4´´5Ѳ½³,£fn~–Î]>¥×èäê™<ª[ô$UÍü1Õ¾ò(UOýž4%|‹ÇÜÔ4,™2ˆJŽ®Óo¼Ú² àYð,7=#lžžÕŽeÏ:4{Â4ðï|<ʨj(§9[~c íJ ¦«ÖP„ƒ†šé?ÔÂ…ëå»T0å›´8v€%h¨8½Ûx‚Ð@[–<Ëžeà¦g„ ÀÓÀ³úÀ± àÙ�\° ÇÇDeÐPP±‡&ô+KаéørËú uo>i ¾C‡§|ƒÒ&ÞÐ4,Ÿ6„ª+Žê·^mYð,x–�›ž6OÏêÇ2^“Á4ðš Q‡ *˜C8|DͬGèD·èýØÛhiÌz'æfzË4¬|骭ŒÎmò‚�Ó7=m VäÍш „±ã�hp ÇÝJÍWªôÚ@ð™-x–<ËÀMÏ€§�gõ�cˆôÜrÖAÃÞÒOµ­,���·¸ÜQ¼V?ëLÓÖ7ºÖgH7í6a.oÅÐk•Àg¶ àYð,7=#lžžÕŽe Îsc!Ñî!×C†�¾Ž÷Å5úÉè€×gøðÐ–Ñ IÆPñÅÃz ®µ6RýŠÿî ŽM¹—ÞŠ¹¾>ƒ¹¬šu¯~+ øÌ–<Ëžeà¦g„ ÀÓÀ³úÀ± Dyö@ÐPß\CówL° ©ÛGµM�§=´W•Ðåy�k!CUÒpÊŽÒ,¼c –$ÜA§G×s‘ŸÙ2€gÀ³ ÜôŒ°xxV8–�Ï—³‰²]øçÚíúÉèàô¥mƒ9hxÿà|Çm- ®žÌ¡š—~¬ çÿŽÖÅ~Þ.,M¼‹Þ�ñUJ›t‹6¢Aƒºà3[ð,x–�›ž6OÏêÇ2áÙ)hàcQDÖ©LmM#dˆÉø¹¶fC0š³–Può-h(Nøkz'æKÐ�¹`d׎hÏêÇ2€gÀ³ ÜôŒ°xxV8–�òžyšO—0‚žFqå¨~²ÿá]%xw óhÞ}‚w¡¯ÏÐðAB×ú û&ßMi­ë3ìÎOmú-О%Ç2€gÀ³ ÜôŒ°xxV8–�Òž�‚^·!J¨j(§¹[Ÿ· ‹v%ЕÖ:½†?u¨nÑ“ZÈP™ø mœt—%dX2e�ãT ´gõ�cÀ³ àYnzFØ< <«Ë@Yϼ•¥9hà­.›Kô“ýOá…<šüѯ,AÃÆcKƒ®ÏÐVšOµsFiAÙ„huì@Kаfî0ª«*Ök[A{V8–<Ëžeà¦g„ ÀÓÀ³úÀ± ”ôÌAƒ2ACkçÚÑÀ¦ãË-ë3Äeަ#å9úYgZö¯íZŸáД{hqÌ�– aûêgèjKƒ^Û´gõ�cÀ³ àYnzFØ< <«Ë@9ϧc¬ACþH¢«�·�ìKš®6PZÎTËh†9[~C•õez :ÚéJÆt-dàm-·Æý…%dx;n Ûý¦^90hÏêÇ2€gÀ³ ÜôŒ°xxV8–�RžO�÷Úÿµ¿/9_WB37?k Ò÷ÌÔˆ DGÃ%ª_òŸZÐp.q½{›%hXùÒ=t±,O¯´gõ�cÀ³ àYnzFØ< <«Ë@ Ï×ÚˆŽ�± Çžˆš !ï³?iS%Œ��§Pl;鿈£™öŠT;÷Q-h(L¸�–Æ ° Ó¥æ+¡�Ø@{V8–<Ëžeà¦g„ ÀÓÀ³úÀ± <ìAïgx±Çu‡YF3$mCE•õδÚ@53Ö‚†ìø!–m-ÓbÐþÍÉzÍÐA{V8–<Ëžeà¦g„ ÀÓÀ³úÀ± <í™G.e N>AC}s -ÈŠµ )Û~KµMAF#t´SÓÖ7´�á|âßQæ¤;,£–MLçNnÑ+‡Ú³úÀ± àYð,7=w6Œ;¶«œhÏêÇ2€gÀ³ Üô4lHOOïêèóω‰‰]?§¤¤h?óy·�‡‡ àY}àXžôÌÛXîn Jgè'û—œ3[¶µŒÉø9å–lÒÏ:Ó^UB—ç=® û&Õ2m‚ËîŒñÔÑÑ»ÑhÏêÇ2€gÀ³ Üô4l0Ã�~#làÎ?ÿnÀ¿ó¨·�‡‡ àY}àXžóÌAþ¡Ö álª~²ÿàQ +÷¿Ò2páÑ e5Á_ß«'²´õ*¤Oâ¾h –LD§òWé5{Ú³úÀ± àYð,7=‡6ðHqÀØ;ÿBp8àöñp@ã�<«ËÀSž ‰öÞw=dØ9€è|š~²ÿ¨n¼@s·>o fÇÓ•Ö:½†3M;Ò¨:y•%ü ­Žh ÖÌFÕGõš½íY}àXð,x–�›žC ŒÀÀíP!Ðñpà %’¥äØ&jËÜ4\Ûq�?<ß±n_–­2(>ãß,Aò¬WèDQ¡c}.'�¦ó‹;×g8<å´8æFKаnáÏèxA¾ãmQPPPPPPÔ)nÑmØÀ�~^¤Ñ ‡æÎ¿¸}<Ü|Ñ@ôÏêÇ2ð„ç˾ÿ—v¹>¢!{Ñ¥õúÉþcsáJËú q™£éй,ý¬3µåtù�1T•4œ¶Å}É2ð¶–»^×kF´gõ�cÀ³ àYnz6pŸƒ{GŸ§T8-àèöñp@ã�<«Ë ê=sÐÀá‚9hàcýHK[-Î}Ñ2šaÖ§ãè|]‰^Ù«gò¨vÎ(:—8Œ>Œd VL¿›.–åé5#Ú³úÀ± àYð,7= xú‚±ý¤QŒàÁéãöñPAã�<«Ë ª=óèsÐÀ£ú9hà@�ƒsÐÀÁÁhÞ³Z[Ÿ¡0á>Zs³%hؘö(5_©ÒkºÚ³úÀ± àYð,7= ¼ ‡ àY}àXQëùâšÎ ÍA/Ù�ð ž*a„ <…‚§R¥£�®dL×ÖgØÿ—~ÛZîßœÜëm-CíY}àXð,x–�›ž6OÏêÇ2ˆJϼÄ9hà(ú1hè¸ÖN™GÒ,£ÖÿšŠ*ê5œéh¸Duo=M¿MMú‚%dX6u0•nÒkºÚ³úÀ± àYð,7=#lžžÕŽeužÏ¦^¸ìJÔZ¡Ÿì{ê›k´m,ÍACʶßjÛ]£íÜ1ª�û(�NJ+bo± k_AõÕÁ×wˆ4hÏêÇ2€gÀ³ ÜôŒ°xxV8–ATy.I¶ FôkÐPVSDÓ6>i Vî…ÚÚ[õδÚ@5/þ€öOþ¥M´nk™ýÁs}2mÂÚ³úÀ± àYð¬6Û‹kiDʺ+>‹†Ïݯýi6OÏêÇ2ˆϧÆ[ƒ†ü‘Dí úɾ'·dÅdü¼+dàõrÎ|¬Ÿ @G;5~’J•‰Ò'q_´„ K¦ ¢“û—ëû´gõ�cÀ³ àY]8X¸}R6ýÙø]åŽø]´¾à’^#2 lžžÕŽeÐµ�|Î4ÕoA�ZX} Õ2š�G7”V_3âZs=Õ/}ŽÊþ†Þ‹ýœ%hxoÎPª®8ª×ìОÕŽeÏ2€gïq¶¶…Jª›µ0aKQ ¥ï­ 7sÊ)yS ů?CϬýJ‘að4ð¬>p,ƒ~õÌAñ'¬AÿÎÇû�Ú¦*m=sа +V[·!í•Åt9õttÊ7iqŒuÚÄ–eOÐÕ–þ¡a€ö¬>p,x–<÷=m×´° °²Q x¤©;Îj�Áøu§´Àà±´£ôÈ‚C4tÖ>ºgú0a§cxnáëD„ ÀÓÀ³úÀ± úÍ3�\(m  Ÿê· �w–à&ÌAï@Á;Qãê‰,º4ㇴ3þË–�!-vÉJÕkõ?hÏêÇ2€gÀsÏhhi׃üs Z`°æÐE-0˜½õ3-0xîý“Z`0jáa-0à°àîi¹Ž�×ÊïŽùʽ3÷êÏ"2 lžžÕŽeÐ/ž9hà5ÌA¯ÙÐOl=±Z[“Áâ2GÓÁ³;ô³�iÚ¶ˆÊ‡ÑÚØA– aÅô»©âL¶^+:@{V8–<Ë@²çª+WµÀ ·´N –ç]Ð ¸pXðÔŠB-,x(õ  NÈqìÀ÷UáûçÇÁ�‡?>~œÆcæÇÏσŸÏ+ÛÊèö8ëTŠ;â²µ`$’ lžžÕŽeÐ瞯Vù §cô“}KK[¥ï™iÍ0só³t¾.ø¶”×Z©au,�Hø-‹¹Ù4|¼h5Ö÷ß�@{V8–<ËÀëžC]¿€;æ¼wÔù:Üæx_áÀ�ƒ�|àÇÈ#!ø1óÈ~ðsâ‘üüx$EOà)¼FÃ-wj#"40€§�gõ�cô©gÞÆrßPkÐp¶¦TÖ—Ñœ-¿± i9S©éjðõ:jËéò뿦œø¿¢´‰7X‚†¼M‰ý²­e( =«ËžeÐßžû{ý‚ž¾o~ üXø1ñcãÇÈ�•3?v~ü\ø9ñsãçÈϵ¿pÓ3ÂàiàY}àX}æ¹¹Ä4ìÐoAÑòmª„2ðŠMǻߖòê©=T9ëG´~Ò,!Czâ�TV¸I¯� =«ËžÕ†;Á#RÐ]ñYÚ_üù÷žâ‰õ l…G7ðãàçÎ�‰G?ðcäÑü˜yt?-ÁωŸ�¢ð*n¶g„ ÀÓÀ³úÀ± úÄsc!Ñî!Ö áâýdßÁ‹=n<¶Ô2šaòG¿¢Â yz�À4ï~—N'ý ­ˆ½Õ4|�2œê«ƒO»ˆОÕŽeÏêÂ�çÛ'Y§|>n-Î=¯ìúü¼80àu$âf{FØ< <«ËÀuÏWŽFEÐp¥µŽfÇ[‚†¹[Ÿ§êÆ z g®µµÒ•µÉtpò×ý¶µÜùþ8jokÖkF7hÏêÇ2€gµàN6w¸¹>dênÇÎ{”¾\¿@2n¶g„ ÀÓÀ³úÀ± \õ|9›({Ðõ �æc}LYMMßô´%hX¹ÿm�È`t4\¢š?>IŸÆý¹%dX2ùv*Ú—®×òhÏêÇ2€gïÁ�pî�¯:XI36—jéç©wN¶îHÉâÅõ $âf{FØ< <«ËÀ5ϵۣ"hØ[ú)Ådü¼+dàõ²NeêgÓVv„ÎÍz˜Þ‹ýœ%hX5ë^ºTž¯×òhÏêÇ2€çè„;éGÏ_¡uGªhî¶2mÀÈùù4$)ŒÑ ¿w8æ+·Æd‰X¿@"n¶g„ ÀÓÀ³úÀ± \ñÌÓ$xº„4ð4 ^·¡áõÞ?8ß2š!iÃ:}©@¯˜–ƒ™t,é~Zs“%hØœ>š®¶ß­"ZA{V8–<÷/ÅUM´©°š^Ï:G/|XL�.:B÷ÍÜë„Rx}^Û€C„ÿx·�>g[³áޏlW¶EÑ�›íað4ð¬>p,ƒˆ{®\ÕïACmS¥nÿ�%h˜¿cÕ7×è5ÐÑN ëgÑÎø/[B†´˜›èðö¹z%o‚ö¬>p,xvŸŠúVÊ>}Y9“yšF/. asòh`L–%µð ¼»Â˜eÇ)qc‰¶8"¯ÓPÛä¿U2Ok¸Ö>ºeâNºwæ^ Šãf{FØ< <«Ë ¢žy+K#dàÂ[]¶Vè'û†â‹‡µ æ áÃCoh#‚q­±–*ß~šÖÅ~Þ4,ŸúT~j»^Ë» =«Ëž#wôóÊ굎?ð:  8Ý^#� &8 à ‚§6ppÑàYnzFØ< <«Ë bž£ hØvr�¶&ƒ2Äeަ¼Ïþ¤Ÿ L{e1Ïþz'æfKа~Á�¨±¾oŸƒ[ =«ËžC§¹­C[˜‘Gð ãViSz³5$O™à©<…‚Yä)<µ"ÒÀ³ ÜôŒ°xxV8–AD<Ÿo òGµÕê'݇w•X¶w–e4ÃÌÍÏҹ˧ô�i-ØB{¿Aio° {7ÄSG‡ÿW¯‚ö¬>p,x¶Â 3òN <ý€;ÿðV�¼u£SXJáEyqG'x±G^ô‘ä𢯀g¸éað4ð¬>p,ƒ^{v ÚûnŪ†rš³å7– aÑ®jºÚýc¨ûtm˜t§%dHŸ<ˆJŽ®Ók¨Ú³úÀ± ¤zæ]xמžÀÛ7òV޼­#O_p º+¼í$O›àé¼%oKÉÓ*x›ÊhíYnzFØ< <«Ë Çž¯µ{Â4ðïÍz÷)¨ØC“?ú•%hØt|y÷ë3´6RÙÛOÒŠØ[-AÚ9P]U±^K-ОÕŽe ²çª+Wµ…y;GÞÞ‘·zä…{º0#ߎ×QàëðõÒrÏk×çû‰vОeà¦g„ ÀÓÀ³úÀ± zä9PÐÀÇû8T0‡ :pøÐíÕethö÷iqÌ�– aûŠÿMím}”ô5hÏêÇ2ðºg9Àë(ðHQðÔŠBm„�4p B)<Â�G:ðˆÞ’’G@ðH/ƒö,7=#lžžÕŽe¶gž"qèkÐPüBŸ <=‚§I˜ƒžFÁÓ)º£¥(‹¶NùKKÈðvì*Ìý£^C]ОÕŽeàϼޝqÀkðšϽR[�×Bp B)¼¯ÅÀ×âµx�^«�ïKEОeà¦g„ ÀÓÀ³úÀ± ÂòÌA¯É`x͆>‚|œ¾éiKа"o޶@dwTmN¡÷'Ýn ÞMþ]*Ï×k¨ Ú³úÀ± ¢É3ïÂÀ»1ðh^˜‘wiàݜ‚P ïÁ»E<³ò„¶{ï"Á£ ¢e…¾íYnzFØ< <«Ë dϼ�eÞ0kÐðÙlý¤ûì-ýTÛÊÒx‹ËÅkõ³�¹ÖÖJEichiÌKаqÁ?RkSßí˜Ñß =«Ë ¯=WÔ·RöéËÚÂŒ1™§iôâm„ž®£0(.[[‡�fLÜXBËó.hë(Ô6©³ûO$@{–�›ž6OÏêÇ2É3 û†Zƒ†³©úIwáõ><ô†e4CÒ†1T|ñ°^#0mµç(gÖßYBÞâòà¦$½†ОÕŽeà†gîèóN Üñ瀃^G�ƒ§À »Â;Dp ÁÁTl/®Õ‚ hÏ2pÓ3ÂàiàY}àXÝzn,$ÚsÏõ�a碊tý¤»Ô7×Ðü,ACêößù¾Wé5ÓP”E“¿h Þ‰Dg 7é5d�ö¬>p,ƒžznnëЦ$ðÔž¢0nu‘6e�§.8¡ž2ÁS'x ¯£ÀS*xjè=hÏ2pÓ3ÂàiàY}àXA=sа{ˆ5h¸¸F?é.§/h#ÌAÃûçw»­%sî“—é�˜[,AúÙC©±¾B¯!´gõ�cµá‘<Úà®ø,mÿn‡KäEyñDîüsÀ‹*òâŠNaA(…uäÅ9œàÅyÑG^ü‘à àhÏ2pÓ3ÂàiàY}àX=_Î&Ê|=hÈDTÝ7£²Nejk2!CLÆÏµ5º¥£�.ú¹6UÂ4ìZ6†::dÏF{V8VnŸd�Òp›ï÷çלԶ{ämyûGž¾`®jám'9Èàé¼%oKÉÓ*$.Ì- =ËÀMÏ€§�gõ�c8zæ �ÃsÐÀÇ\†w•àÝ%Ì£x÷ Þ…¢;Zª?£O¦ßk Ç S»ßÒkÈíY}àXMx=…{{±ÃƒQxAG^Gá‰ôc¿þ ¥åž×f¬ºrU¿'M =ËÀMÏ€§�gõ�cøyæiæ �§QÔçé'Ý£ª¡œæn}Þ4,Ú•@WZëô�©:ºžVÇ}Þ4¼—8˜.Ÿ?¢×hÏêÇjp¶¶EYðÜû'µéNÁA°Â#x¤�xà-)·Õh×ÞíYnzFØ< <«ËÀ♃^—Á4ðº .Sx!�&ô+KаñØÒ�Ög8ñÑdZs“%hØúÚ?ÐÕ–½`ОÕ޽ ¯±À»5<³òÝ3}�c€ðg¿w8æ+wÄïÒÖfà5ø:¼fP´g¸éað4ð¬>p,ƒ.ÏåoZƒÞ겂†MÇ—[ÖgˆËMGÊsô³�éhk¡óÿÑ2¤M¼‘ެ›¨×fОÕŽ£xê¼-d(;Að: ßœ±‡n‰É²¿#.[ÛY¨ Ú³ ÜôŒ°xxV8VœÚíDFP{Ö]D¹_¹2AC«»;74]m ´œ©–Ñ s¶ü†*ëËô�i¨:Ik’¾d VÄÝF­Õk;hÏêÇÑ/°È‹;&o*Ñv…g]äÑ©ðÚ �,8D‰K´­$�EyôÂý³öÑ-wjk8 hP´g¸éað4ð¬>p¬04dßn vêÿ|Èõ á|] ÍÜü¬%hHß3S[ ²;ÊòVÐÒXë¶–½ø5jª.Ñk'ОÕŽû^Ì‘·†ŒÉpì!šŠ‰ŽYÌÑ Ð–eÏ2pÓ3ÂàiàY}àØt4•$w.øhrw. ½õÜÒÖD‹s_´Œf˜õé8:_W¢×̹“[è�Ä»,ACfܨöÓT½ˆhÏꣲãŠú֮ŇÏÝÖbŽ©;ÎöÛbŽn€¶,x–�›ž6OÏêÇQNõ&¢½÷YCž2qò¹Î­.C¤7ž9Pà`Á4pðÀDwøôEJ‹¹Ñ4ìJø*µn×k€H‚ö¬>*96s·º(¬ÅŸZQ¨­ÓPX©î¨(´eÀ³ ÜôŒ°xxV8ŽRšKˆ F[C."ªÏÓ+…NO=ó ž*a„ <…‚§RtGkS-mJ{Ô2,�@…/ÿ�Ú«º zÚ³úxÙ1/æøzÖ9m1Ç!I»Ã{Ἐ㪃•Q»˜£ -Ëžeà¦g„ ÀÓÀ³úÀq”q­�è³ÙDÙƒ¬!î;‰ÊßÔ+…O¸žy±ÇÌ#i–Ñ ¼(dQåA½F`.•çÓÊéw[‚†÷co£Š·ŸÆú .ƒö¬>^qÜÜÖAÙ§/k‹9>ºèHX‹9Ư?ã©ÅÝmYð,7=w6œi Vî…ÚÚ[õÎ\mi í«Ÿ±„ ‹cn¤ü¤¿¦ÖCôZÀmОÕ'ZóbŽk]ìZÌÑ)L°—Á 9]‹9æ•Õ+³˜£ -Ëžeà¦ç aƒwþ¹Ø7àP€Ãþ�GFDêx8 qÈžÕŽûž2q6µsŠ„9dàßù8Ÿ�¡xÎ-ÙD1?ï x}†œ3ëgSWUL¼òmKа2v •ÎIí'ôZ /@{VŸþrÌ‹1òbŽÏ¬<ÖbŽ\?-÷¼Ò‹9ºÚ² àYnzîqØ`Lq0Os°‡‰‰‰Ú¹H4À³úÀq?Á#xä‚9dàÂ#x¤C„ æ™G-¬>�jÍÀ£J« õ�)9ºŽ–L¾Ý4l˜t'Õ¼ý u4\Òk�¾íY}úÊ1/æÈ#žH?ÖbŽ/|X¬-æÈ#@ÏA[–<ËÀMÏ= Ìdddhჰô5ð¬>pÜÇðÚ ¼ƒ=dàµxÍ—ä¹¶©J[�Á4,ÈŠÕÖmFGGíÝo Ò&Þ@{&…®|ôÖgè'ОÕÇ Ç¼V¯™`,æÈk)8… æÂk2‹9®/¸$z1G7@[–<ËÀMϽÝÀpH`ŒÐ RÇÃ�_4”ÐË…ƒÓ¨#ëKÈбó6ª:0‘Nw¼�›åO>¢øŒ³ Kwþ�N:Ö7ÊÑC9ôÞ+o Þ‰¹™N$ÜOŸmxÓñ6(((ÑSòŽÒÛ[�Ðÿ·l?}oÎnºuâNÇ@Á\n‹ÝI?Jɥ߽»Ÿ–o?JGŽŸp¼6 Šq‹^‡ <²�G0¼¨£ÓÂŽ‘:n¾h z€gõ�ã> >�èàC–�A+£‰šKôJîb÷¼õÄjmM#dˆËMÏîÐϦüÔvZ>mˆ%hXûyª˜ýcj+Í×k�þíY}z☧4ðÔžâêbŽ¢êMz¥¾ÁðÜÒÖDé{fZF3ÌÜü,�¯ë>ôÈÿÓlJ‹` ²â‡Pí¢ÿMuÝo‹ ÜíY}BqÌ‹1ò¢Œ¼8#/Òè&ØË}3÷jõyH,æØÿ -Ëžeà¦ç aƒ—Aã�<«»Ä…åD9ƒ­!CÖ@¢’d¢Žf½’û_Ú2,‰¹‰ŽM¹—>H k­è˜D hÏêÂk*ŒH9@wÅgi£øw†·�4säm%ÃYÌ‘·­ÄbŽÑ Ú² àYnzFØ< <«G˜+G‰òGZC.G%j*Ö+õ 4LÊü¥%\0 O¡Øt|¹^30—ÊóiÕìû,AÃ{±Ÿ£²¤¿£æ¬%z-- =«  ·O².ÚxkL}ï•ý!/æøÈ‚CÚbŽ› «±˜£@[–<ËÀMÏ€§�gõ�ãÑÞ@t:ÆÊDîÝDUëôJ} �h4^ÈÓk¦h_:-™2È4|÷çt饑tõdŽ^ DhÏjò7/ç9†�Ê�“wi»JðîÙ§/k»Mo�¶,x–�›ž6OÏêÇ rUg¨`8t8ßBô×ÚiRFàQ Áhok¦�ï�³„ ‹cn¤ƒ“¿N—ç=NíÕezMm =«ACK»¶�ä¸ÕEt÷´\Ç@Á\xêĘeDZ˜£B -Ëžeà¦g„ ÀÓÀ³úÀq/h,$:<Ê2pác|®ŸàQ ³>G¿w¸ÌüäY½¦?õÕ%ôAÊpKа"öV:�0”êWü7ÖgˆrО½ /Ê8w[�ZxX›ö` ~oúÙT¾”´›Š«šô+•@[–<ËÀMÏ€§�gõ�ãÀ <–$v.øhvé\²Ÿ¨j(×|4 öÀ!.óq:t.K¿…•ÏŽ­§ôÄ;-AÃúI_ ÊÄ©iëz-Í ={žÚÀë'ðv”¼„S˜`”[&줭ÄqÙ´æÐEýj@5ЖeÏ2pÓ3ÂàiàY}à8Lxý…=÷XCž2qj|çV—ýog¹¾` ÅdüÜ,LþèW´ò@Š6ÊaâºÇ´ NACGGíÝo Ò&Þ@¹ñ_¡šSkÁ½&ˆvО£›’êfz3§\[OÁoô‚­pÁALð´Šûgí£[&î¤{}ç4¨ Ú² àYnzFØ< <«‡Hs QÁhkÈÀåàCD ùz¥¾'ï³?QÒ†1–��Ëê©Tß\£× ì¹±¾‚>^4Ê4,‹¹™N$|‹jS~Fí'ôšÀ  =G¼-å–¢ŠÉplâZQÙ\¢ìAÖ�a×�Dåovžïx+Ë™›Ÿµ„ \羨í@ŒòSÛiík#hIÂ]ôÞËCiùô¯X‚†÷b?Ge CµsFQ[ÙýVÀ« =»Gni%o*¡‡R:† æÂufl.¥¼²zýÖ‘ŽeÏ2€g¸éað4ð¬>p¬s9›(o˜5dàRøQk…^©oá aÑ®¿��w—(ª<¨×  K&ßn Ì哸/RUÒƒT·èIêh¸¤ß x´çÈQÛÔ¦�FàQ ƒrC£ðù§VÒò¼ Ú¨7�cÀ³ àYnzFØ< <«�xÇ$p `8xà¢hºÚ@™GÒhÂÚŸZBžB‘u*S›R kS¿ç2¼óg´òר:é;tem2]kkÕo¼>³{Gþ¹mDÂÈùùÚú NÁ‚QF¤ÐÖiàõx݆¾ŽeÏ2€g¸éað4ð¬>bó”žÁS$Ì!O¡à©ý4e"·d“ßV–:¼p>]i­ÓkuO]U1½=éǰá�oÐÖghÞµL¯ TŸÙáÁk(¬9tQÛ‚w†p ŒÂ;Kð¼VCE}ÿtp,x–<ËÀMÏ€§�gõé˜yäÅÍ!^’‡ìJ« )eÛo-!—ù;&й˧ôZÝÓX_A»3Æûí4a.ïÆÜJWOíÑoTŸÙÝSXÙHs·•Ѩ…‡»½À»Kį?CÛ‹kûtôB0àXð,x–�›ž6OÏê#Ê1oWÉÛVÚCÞÞ’·¹ìx+ËysüBÞÊòàÙz­î1B†·ãZ‚ž2aþ}qÌMt8á^ýV@5ð™íOs[m*¬¦çÞ?I÷Lßã*…G/<–v”ÞÌ)§’êfý ÑËžeÏ2pÓ3ÂàiàY}Ä8>ŸF”3Ø2d $*�AÔÑ÷�жöVm+˸ÌÑ–�ÁØÊ’χBkS-íßœLK¦ ²„ \x§‰¬ø!´2öVz{â Úˆ†ÃS¾Aµ¯þ\¿5P |fwR\ÕD¯g�£G¡�1YŽÁ‚Q—^ø°˜¶ÕhÁD´Ç2€gÀ³ ÜôŒ°xxVå7ä|È2p)MÔ\¢Wê[Ž”ç8ne¹lï,ªn¼ × Ž2¤'Þé2¬ŽHG§|S[Ò^jfޤւþÅÜGêg6‡ÄdžÖ§PÁ(>pÁa‡^ÿ/ËžeÏ2pÓ3ÂàiàY}”uÌS&N�'Ú9À2칇¨j�^©o©¬/£…Ùñ~!Ü-¿¡â‹‡õZÁiok¦#Y©´lê`¿�aEì­tpòש*i¸,\Ný5}ò]ž7šª§ý½6¢AƒÚHúÌ>[ÛBi¹çµi<ýÁ)X0 OŸàië .i‹Bzü¿,x–<ËÀMÏ€§�gõQÒñ…åD»‡XCž2Q’Ø/S&x+Ëu‡9ne™sæã�¶²4B†åÓ†ø… ïÄÜl j猢–}k|ÏõúuÑ–e ²g^ ‘jäyáF§PÁ(¼ðã# Ñì­Ÿi BªÚ² àYð,7=#lžžÕG)Ç�…D‡GYC.|ŒÏõ1"ðV– ëm Œ­,9„莎Ž6*Ú—N+_ºÇ/dXs3í™üªJz°sŠÄK?¦¦it­Í½´e¨æ™·˜ä­&Ç,;NwNÞå,eHÒnm KÞÊÒ룂�¶,x–<ËÀMÏ€§�gõQÂq»¯Ó~&ÞÊDîÝD•«ôJ}ËéK4wëó–�� O£8_×ýZFȰjö}~!Ã’˜›¬!Ë? ÆOçÓµæzýÖþ -Ë@Ϲ¥u”¸±„F¤p ŒÂ£FÎϧ›K)¯,ð{_5ЖeÏ2€g¸éað4ð¬>žwÌë/p¨`8t8ÓBô1µMUÚB�ö�aú¦§éй,½VpJŽ®£R†;† ÙñCèBâ·µ�¡:y]ɘN —ô[mY^ô\uå*-Ï»@O­(¤Á 9ŽÁ‚Qø<×ãúµMmúd�¶,x–<ËÀMÏ€§�gõñ¬ã¦b¢#�ZC.ù#‰®Õ+õ¼UåæÂ•ÚÖ•æ��çã¡le(dH›x£5dð•†Õ±Ô^]¦ß²{ЖeàÏ<�G$ðÈ¡à,…G8$o*ÑF<´e)À³ àYnzFØ< <«�çó�%É� >šC†œÁ� CöžpÜ\BT0Ú2p9øQ}Ï;à=…w‘àÝ$ì[Yò®¼ûDw[Y^*ϧÍé£C†Oâ¾He s=dHýµlÑoÙsЖeПžyôÂú‚KôÜû'éžé{C£ ŠË¦Ñ‹ (-÷<�­mѯBmYð,x–�›ž6OÏêÕŽ¯µ}6›({�5dØu'Qù›z¥¾ƒC„œ3Óä�~e 8tXwxQ·[Y ¸lœt—%d¨�3ŠZö¯õÝid¶ðC[–A_{.®j¢×³ÎѨ…‡i`L–c°`”¡³öQLæiÚRTCÍmú@¸ -ËžeÏ2pÓ3ÂàiàY}¢ÖqÍ¢}C­!—¢qDW»_!Ò_w¿ö;P·Û3‡¼ða1Ý7s¯c¨`]tD #8”‘ÿ/ËžeÏ2pÓ3ÂàiàY}¢ÎqkQáSþ!CÞ0_¯=W¯ÔwT7^pÜÊræægéHyŽ^Ë™úê’€!úØÏ[C†@M[ß kÍõú­{ ·Oʶtïˆß¥ ujâF{.©n¦7sÊé±´£ÚôóûÉ^xúO£ØTX�Ñ .�ÿ—eÏ2€g¸éað4ð¬>Q㘧LœMíœ"aøw>ÎçûÞªrã±¥~[YÆeަ­'VÝʲ±¾‚vgŒ§·ãú… kcщ„ou… ÕÉ#èÊG/QGCä€ï¥pì~.6[[´�ÿêüÌÊZáÅùx{A^Ð�wà¡Ù§/kN.q|a9QÎ`kÈ�5�¨$Ù×oÖ+õ $,Î}Ñ/dà­, /8Oqà�¡h_:­š}ŸÈ0i í™üªJz°+d¨yéÇÔ¼k]k‹Ü_°yx?Opê4òŽÜ1åÎg ¤¶eÞ~Ñ}!aêHw…G=ðsåi"’×¶ð2øYð,x–�›ž6OÏêãªã+G‰òGZC.G%j*Ö+õ -mMŽ[Yò ÞÊ’§T8qrÿrÇ�aɤ[('þ¯¬!Ë? ¦m‹èZs½~ëÞÁsåù/ÚüWo§Ž#w‚yªD(•F[ŽFxá…©#<†GJ𴎳µ-ú3^mYð,x–�›ž6OÏêãŠãö¢Ó1þS&rï&ªZ§Wê;ò>û“ãV–+÷¿p+Ë’£ë胔á!í”?„.$~»+d¨NA�¿L —ô[÷îÄrG5ÐT ž6ÀS ÂùË4ÚrôÒÛ©#·Äd9¾Oþ*9£mYð,x–�›ž6OÏêqÇ•«:CsÈÀ¡Ã™øÎ¢)«)¢Ôí¿ó øŸs¢¬p“cÈðvÌÚ9Ù2øJà Ô^]¦ßºçp§�;˜Ü‰tê8òT  zºõ Ú²ºð›ÏÇ[§yÜ—ñÅ@At€¶,x@ÜlÏ€§�gõ‰˜ãÆB¢Ã£¬!>Æçú­°ú@ª_ÈÀ[Yò('ÊOm§õ ñbÐöÉC¨½vÌ <–$v.øhvé\²áu¶�\ã·•%¯Ó°¾`‰¶nƒ�ÊÒ\Ç�!-æFútŠÈP÷ÖÓtõŒóB’áÀ‹9òH±à2ðb�Ü�ŒhË2€gõ�cÀ3êàf{FØ< <«O¯óú { *ø”<+LívmÝ…ö¬»ˆö?Htê÷�S$Ì!O¡à©}8e¢²¾ŒíJð ælù UÔku† G²Riù´!þ!CòÊŸö7T•4Ü2\~ý×Ôz|›~…ÐÉ-­£1ËŽkÓ"ì{"ým)ªÑkG'hË2€gõ�cH÷Üzäª�3Êò¸¥fö?i;OpŸˆûFF`Àý&óȆçãNçÌ»-Ã}@{?0PØ`<.¾=?Nó9&P]#„†ÓãåÀ�okÀ×6#Œ�q³= øMÄBío2þÙjÔaÜ>øÏNð¬(4dßn vš~æÂ‹Bòâ�}oe™y$Íq+ˬS™][Yvt´Ñ±Ýo:† ï$ÞEy3þÎ/d¨Mù™6´’:B_œ‘G'¤åž8UbpBů?Cgk[ô[D7hË2€gõ�cH÷|yÞã–ÿÇÝ,¼ •î+™ÿ Ë}¥@��ùwÂùv¡Þ–áûâb†¯cÜÞ€ûnFŸ1×±Ÿ T×þXøgãñ�‡oË¿›ák}GÆ~{}`ÅÍöìûÆÞ=ö7 4¿Á ¡nüg'xV”ß³ æÂÛ[ò6—}‡¹%›·²|ÿàü®­,9d(Ú—N«fßç2,OúsÚ7û;T•ô õ „þ‹km××vè8Dà0Á)d>w¿BDãT‰` -ËžÕŽe Ý3ÿß]óâ,ÿ§»Rx'ªORõ{µÂ}3£ƒíÔù7:à|ÎܱgÌõƒÝ–éϰ! �Ëüxûu6ÇÍöìûæÞ=ö7™]` 7H¤�‡þ³“<+�jȺÅ9hØy£¯Wßw‹ž¾T@)Û~k ¸,ÈŠ¥óu%z-_½ÃkC†ô)Ÿ§Ýs¾ë2¼ôcjÚ¶ˆ®5×ëWèžìÓ—µéÁ¦Jp¯‚¶,xV8–<‡NGm9UOÈò=@û.0ã‡aï4Å}"î\ý3þ—ûOf¸ƒÍý'.üs ¾U°Û2Æí ì¿ðã1ß—¹ŽSØàT—�™ûœÁàÇa\Ã6ðqãþøzN�\ÇÍöìûöÞ=,É,>RáA¸ÇÃ�_4o”“EÇ©üðÔœã°Ã„©\Ýõ5ÇÛGº(ØK ÿä¿.CÒGciþU]õr6¿AïÎzÀ/dX -íôfN9 ›“ç0p’´›fl.¥ŠúБô"hË2€gõ�cÀ3êàf{6x|Êž=•£D'ž!Ú9À?dØsÑÙÔ€ ?ºå˜·²üðÐ~[Y&¬ÿ5åœùXÛ…‚C†Íé£�C†×LggýÐ/d¸üÆj=¾M¿—ÀW5Ñøu§N•x(õ ­:XIm×ô[¨ Ú² àY}àXð €:¸Ùž6OÏQÎål¢‚Ñþ—ýÉ.,'ºÖ¦Wv&ÒŽ9Dà0�CsÈÀ¡‡BÔUÓ–eOø… \6½ö}ö‡òRA-3}wЮߓ3› «é±´£ŽÃÀ˜,zjE!å•…¾K…* -ËžÕŽeϨƒ›íÙ÷�_Mð!(xŽRªÖå�t=Bti½^±{"阧EðôsÈÀ…§Qðt ¶¯~F¹à2¼þ#*MýW¿�¡vÎ(m¿íkm�§9ðT‰×³ÎÑÐYûC†»§åÒì­Ÿ)?U"hË2€gõ�cÀ3êàf{ö}óW|Êž£žQ‘N´o¨ÀÀÓ'x„C}÷k؉„c^à‘z´‡ ¼ $/ ÙX_AÙ<ç2|ôê:½àç~!CÍK?¦¦m‹èZsàQ…•�ô‡Å4(.Û1d9?ŸÖº(fªD0ЖeÏêÇ2€gÔÁÍöŒ°xxŽxg‰²¹�w–8ùQS±^9|z㘷ªä-+yëJsÈÀ[[ò—uµe´;c<½7Ð?dxíïéÔí2¼øjÜ8—®5úžwÖ\¢Gq xªÄ¸ÕE”®A¯ ´eÀ³úÀ± àup³=#lžžûÞ5âL|��%ýv–è =u|ðìš¾éiKÈÀeEÞºXsŠò6%:† kþð·TôöXªNa |¿_Y›¬í“íDmS¥î8K÷ÍÜë2Ü3}�6U¢êÊUýÀ Ú² àY}àXð €:¸Ùž6OÏý�R(×9jÁ2ðèÞY‚G;DˆpóÚ ²býB†”m¿¥¢s¹´s2¥'Þé2|0w�XöÚÈKÈà+ +'P{¥ó茣ç¯ÐsïŸ 8UbÔÂôîH¦JtÚ² àY}àXð €:¸Ùž6OÏ}H]nà�%ò†u®×ÐÍÎ=!TÇWZëèýƒóý¶²LÚ0†vŸÊ¤CÛæ8† «f}“ŽrÈðÒ�ýB†ú%ÿIm¥ùú=\‡ƒ8Hp xªD€Ð@[–<«ËžP7Û³¯— &ø”<÷¼sï á2ðqÞyÂEºsÌ[Yf�ʤÉýÊ2pè�‘¿�l™–Oâ2ÜK+CÕsþÙ/d¸üƺz"K¿‡ëðT‰¹ÛÊ´)N!çó\„Ú² àY}àXð €:¸Ùž}½5Á‡  àÙ%x„�TØ?Ü9dà—³õÊîÌqQåAš³å7–��Ë¢ìÉ”—•â2𱃫þU§>æ2¤þ‚ZfêW¿/æÈ#xÄ‚SÈÀ‹Aò¢� ç -ËžÕŽeϨƒ›íÙ×kP|Êž#L{Cçš ¹wû ¼FÉgˆ õÊ}ƒ“cÞÊ2}ÏL¿�aæ'ÿAÛ¶$ѪÙ÷9† ùkþ‹ªøï0Q;g5ï~—¨£]¿‡Î©¼-%oOé0ð ¼­%oo zÚ² àY}àXð €:¸Ùž6OÏ‚w�àÝ#í,q:&";Kô³cÞÊrã±¥Ž[Yf~2‘VϹß/dàuö½÷ŸT�ö~!¯Óдm]k½ðn¼kÄÝÓrCÞm‚w�ÀT‰È‚¶,xV8–< n¶g„ ÀÓÀs/á�%Š_¼³DÙ܈î,ÅSʶ(>ãßhîÖÿK,¥iŸ´„ \ÞÉx†ÞŸûwŽ!CÞ‡/PÍòÿò^ü5nœKךëõ{#Ê+«§q«‹N•x,í(m*¬ÖkƒHƒ¶,xV8–< n¶g„ ÀÓÀsiÈ':öÑÎþ!ÃÞûˆÎ§u4ë•û&eþÒ/X0—yüŠV¿ú _ÈðvÜ@ÊYý¨ê½‰~!Cuòº²6™:ê.h÷ÃS%V¬¤‡R: wNÞEã×�¢âª&­>p´eÀ³úÀ± àup³=#lžžÃ¤zSà�%>äúΡ’²í¿†ñ>J3ßýôn �aÍst)cª*؃††•¨½ªD»~E}+ÍØ\JC’v;† Cgí£×³ÎQCËõ5€» -ËžÕŽeÏýCzz:�;¶«ðïvµbÀuÌõì×V—áóÙÙÖEÁSRR(##Cÿ-ºàçÂÃÍöìëa¨ >eÏ!À;KT® ¾³Dív½rÿr¾®„2�¤Ñï?|”¦¤Ÿæ%ßA ão¡yS?OÓþ-½:s°_È�;€¶¿û$U®ŸA53ö ê—ü'µ•Ñ®ÏS%žZQè8UbÀ„�4zq¦JôhË2€gõ�cÀsÿ`Nž<é×±æc<ÿüóÚÏŒ=@°ÿn'ìç˜h ø~ùþ�°!|ÜlϾž†šàCPðžÁ;Kð´{ÀÀÓ'xg žNÑÏ4]m œ3Sêößu�`HHÿ>-нÉ/X°—í+Ÿ¦‹Ÿ¾J5³ÿÉ/d¸üƺz2‡šÛ:hyÞ‘rÀ/`àÂS%b2OSIuÿMhËR€gõ�cÀsh9Û¹­8OUåŸ{€9 à0�C3|ÎèŒa€=@°ÿÎp�¯çt.PØÀ…oÇÅ|ó9>Î?ó¿Nu�c\ì÷Áðs0ßÎü;#P1~7›íað4ðìÀÕ*¢’d¢œÁþ!Cö ¢Sã{üŸ\$)ªp,ñž÷ õÿÎåVáQ©:ö0Àü;ÿktÖ�bœ7ê0öß®ëL0}6Ãx. †Èãf{ö½ƒÕÿÙÉ@´g^ÔñÈ£Îÿ1ñb�×t.ÙO”VÒûçS\æhÿ€aáßÒ¼™_¦·bnt ¸¤ÅÜ`ù}qÌMtxÊ7®‡ /þ€Nf, øu'´0Á)d>w?¥åžÇT €ÏlÀ³úÀ± Ä{æÑN#I#]øI§cô;õ ¸£mtäí�nîÐsÇÞ~ûïÜqçz ߯ÜY·ÿn`¾ ß§¹ŽSØàT×xl¡`<Æ6ðqãþøzÂÇÍöì{« þ³“�8Ïpˆhg úqg‰úæÚzb5ÍútœcÀðêôÁôÇ Cæ‚‘t$+•.¼>š ¦|“VÆÞJoO¼�Þ�¹U†kÛZnI•~•–tªDöéËú£^ŸÙ2€gõ�cÀs4—e}Îÿ;¯ÛÀS`ÃÀèLÅèt#ìp�ÔÔTK§>Ð5 ¸ãn>ï4ú€oc¾Ž=\°ÿÎÅ^׌ÂÏÁN óÆ1¾†ù:ü˜œ^7Û³ï�®&ø”�Ï< ‚§CÚY‚§QôÓÎ×Úéй,JË™JÖþÔ!`øó€o[ùñ¢Qtl÷›t¥¦”®žÈ¢†¨:é»ôÑ”_Óð ËéÏ¿�þnâ»ôÁ”1ôÊäèÁ—sý.¼$/É Cï�ÏlÀ³úÀ± à9LÎ/&m:DW¹�èÜúIµ±‡ úp³=#lžFyϼ #/ìè´ßã!ÃLÅ#ÅùºZwx%¬ÿu€€Á: Â0îI£æ+UtõL]ɘnÙ¾’ƒ†ÛÆj n0ýl.¼Nom‰©ÞŸÙ2€gõ�cÀsàç^ï,ýôÝ­?@Øý¸Ùž6O£¬gÞš’ƒN¿í!¯LÌ[[ò—}Ì•Ö:Ê:•I)Û~Û0Ä®úGšöÆß„0´�;F�çRíÜG»sùöÄwƒ£ðT Þq"·´NdÀëà3[ð¬>p,x@ÜlÏ€§QÎ3O…8ñLà�%xA¢>ÞY‚§I^È£e{gQLÆÏ»†é †Rê‹w9† \ìC{e15m}ƒ.§þÂ1`¨Lú­~i<�Mù(à(.36—bª„‚à3[ð¬>p,x@ÜlÏ€§QÆ3/êX0Ú?`à‹Aò~Î}¼³DUC9m<¶”¦m|²ÇCGm95íH£ËoŒq ¸¬ŸùÿÑ3)kiðä¬ë¡Âï­ƒQ¾9s¯þè€jà3[ð¬>p,x@ÜlϾžŒšàCPž÷\µŽèàCÎ!áGˆª7éû†–¶&Ú[ú)Íß1!ä€áí¸�´9}4íKï ê.Póîw©îígÃ.šþ$½ðêjº;É0ØÊ�¶�&îˆË¦5‡.ê�¨>³eÏêÇ2€gÔÁÍöìëѨ >eàIÏ< â|Zà�%Ž=Ñç;Kœ¾T@+÷¿Bq™£Ã Nî_NW[èZc-µì_KõKŸÓ¶¦t r’ÿ�âR—ѽÉþÛUå¾™{)yS W5Ñú‚Ktÿ¬}tËÄ�t¯ï8‚µÁg¶ àY}àXð €:¸Ùž6Oã)ϼ³DÙÜÀ;K¿Ð§«×6UÑÖ«iæægiÒ»?¢óï/`hm¤ÖC¨aåªyñŽáäG))%�œá.p¹{Z.ů?CùçôGfmYð,xV8–< n¶g„ ÀÓxÂskÑé¢]wú‡ |¬$±³NÐÖÞJÏî E»(þÝGhæ¼ûhÞÔÏ;† \ü†¶Vj=¾�>H š; G“þ™^N™OÏÞæ.pœ�C/|XLÙ§/ë�,0hË2€gÀ³úÀ± àup³=#lž&ª=7vî,Á£ì!CîÝ�;K´;ÿ5?Ò”Õч‡Þ iïÿϰêh§«§öЕŒéTóÒ�†“‰#éõ”?Ðÿxy‹¶-¥SÀ0(.›žYy‚¶ÕP[Ç5ý‘uÚ² àYð¬>p,x@ÜlÏ€§‰JÏ—³ƒï,Q‘Þ';K\i­£Åk)%óÉn†%SѶ•OÑ©üU�ƒ�¶Ò|jÜ8—jçŒr Ê¢·þð"ý|î'40Æ9`“Ec–§uGª¨¹­C»n¸ -ËžeÏêÇ2€gÔÁÍöìëý¨ >eUžyg ÞAÂ)dàã—ÖëÝ£ãZ;Tì¡ô­¿§—æýuЀañ”Ûµ€¡äè:jokÖnß^q‚š¶¾Aµsu Î'}ŸÞ}y�MYOƒ&9ï$Á#K;JËó.PCK»vÝÞ€¶,x–<«ËžP7Û³¯¤&ø”A¿{æ p,x@ÜlÏ€§qÝs}^çº ;ø‡ û†v®×ÐÑÙ±�4•g÷ÑG«Ÿ¢EÉw9‡ ¾²hò@Zýö(:u<“:::w¸¸ÖÖJ­Ç·QÃÊ TóↂäŸÐ˯. ïÍØæ.p¹{Z.�_wвO_Ö®Û_ -ËžeÏêÇ2€gÔÁÍöŒ°x×<óÎ�v–ÈÙ¹ó„ TœÉ¦­kÿ/¥Mû Çp�Ë›ñ·Ò²E?¤üƒé]u´ÓÕS{¨áƒª™ñ°cÀprê?Òë) ÎÚê.pœ�CãVÑ–¢jë¸Öyí~mYð,xV8–<÷ÙÙÙ4vìX­$&&êG;ÉÈÈè:Ç%==]?CtòäI˹””ýL'ÁnË?›gø¾ù±ƒïƒ¯ |=s]ãqtwû@÷a¿Ž›íÙ×sR|Ê ¢žyg‰ ˉöwx„Ãåà¬=�†ü_Z<õÏÃ. ão¡?¾>œvä¤PëÕFý–Dm¥ùt壗¨fö?9 e‰Ñ[¯Ì Ÿ¼¼YÛ’Ò)`—MO­(¤uGª¢&`0ƒ¶,x–<«Ëžû‡çŸ¾«“χ w¬¹snÆè¬…Q—áNºq�`·eB ¸¾9Ä'l0Ã×5®c¿¦�žÞ°âf{öõ Ô‚2ˆˆgžÁ;Kì¹Ç?`àé'ž!ºrT¯8`ÈÉø¥'É1\àÂüԿ¦Ì?M¡ê+×�l¯8A�çRíÜG†óIß§wçÄÓÏçl �1ÎÃÀ˜,½¸€–ç] æ¶ýÊÑ Ú² àYð¬>p,xŸÚ¦*Ú[ú)å–lÒ~î æ°ÁŒÓ_òùw>ÞÝ_ùƒÝ– %làúP…ƒ ¸ÇÌ÷a>Ç×6B¾¦Qß^Ìa‰�ý>ŒÇi\Ïø™�uÌÇ�cüº˜¯9kÖ,Ë9¾-ŸS 7Û3ÂàizåYÛY"9ðΧÆGtg vgŒ§eÓ†8† \LHxõ^Z¾á7tòÂAý–DíÕeÔ´õ ºœú Ç€¡2é{”1ûûJ& š”å0ðȆQ SZîyßrúô €¶,x–<«ËžÃã@Ù6ŠÍøMÊ4Ê/i_éýlè�q#0à±½3οóq§sf‚Ý– %l`ìqþÙ¸×5~føœù9ðïF`¾Nw�{óuø1ókدÇ?zöúæÇÆõ�×Ç\O%ÜlÏ€§é‘gÞY‚ƒ„@;K|6;b;KÃò¿â.pá€aö+_§kÿ—–x·´un)ÙQw�šv¤Ñå7Æ8 \6ÍGã^YCƒ'; \FÎϧÔg©êÊUíº^mYð,xV8–�tÏÏî ¤ chü‡ÿêjIXÿkÚQ¼V¿×ë©7:üæ±�˜Ïq'šoêm™Þ„ 掹0ösæßÍ×1ÿÌ�ÉxìÆc³_Çx\æãö:ÆãàzÆõ¸ªo~þÆmUÃÍöŒ°xš°<7äã¼³ÄÞûˆÊßìõμ`㹓[´€aÅô»Ã.FÀ0kåOh}Áªj(×n­±–šw¿Kuo?ë.pÙ9c,ý×+ïÒÝI�†ás÷ӌͥt¶¶E»®—A[–<ËžÕŽe Ýó¬OÇ9†n”¸ÌÑú½Zá°ÑÖùçsæÎ3c®ß]p-aƒöë�Ë|Ü^ÇxXÏ!X}#x1®«"n¶g„ ÀÓ„ä¹f ÑáQþ—#:w–àÅ!{ˆ0ì|-›:Ø1\àb IïH š; '§þ#½üÊkôðìmŽá—Á 94nueŸ¾¬?2µA[–<ËžÕŽeÏá±§ä-\à)\øç]§?ÒÏ‚h‡Ãs¢n¶g„ À›ð¨„#¨=ë®ÎÑ —Öwî,Á <ÚC^’„l.Ño:á ñïüPò––3•�Ë¢¶«MtõÔº’1�jfÿ“cÀP–øÍŸ;‹þÇË[´#œ†AqÙôÔŠBÚRTCm×ôG'´eÀ³ àY}àXð><Â�G¸r wDè_x䃪S(7Û3Âà=8hȾÝ(ì4ýl8€sg‰ö¶fúìØú°Ç‹m=±šê›k¨­4Ÿ7Î¥Ú¹�: 瓾Oœ�D?Ÿû ŒqÆdÑèÅ´îH5·uè�NhË2€gÀ³úÀ± àup³=ûzdácžãÂÅ<¬„çÇÍó]"uKÀ0mã“´éørªª, æÝïRÝ[O; \6Íx–^H}�îN 0 Ÿ»ŸRwœ¥³µ-ú£N -ËžeÏêÇ2€gÔÁÍöìë¡…<ÚÀ(F0`Œ­C"u<ð!èZ+Âr¿N”u³õXöD×èµnÀ0éÝu ¼‡ò²½³èDYµì_KõKŸs ¸ì|ñß)îµwèÞdç5¸ �µ�fl.¥âª&ýÑ�î@[–<ËžÕŽeϨƒ›íÙ×Kë Ó6€náp�§:œ|ŽhßPk`¨pÁa‡Æb�¼ûľûéÚÎ[:G4Ø‚†«- t*U�.©ÛG»N®¥Ë×RýŠÿ¦êäŽÃþäŸRÒ«oÓƒ3œÃ.wOË¥Ä�%”®At ЖeÏ2€gõ�cÀ3êàf{öõäzoÂk+pH`ŒÐ RÇÃ�_4”þ/gŽo£ŠCsérÞXjÝõMç0ÁT®í¸‰šs†QMÞPùá7¨¸0Ïñº{w,§•/£% wÑ»³Ð~?^�OYÏ¥_Ei“nu ¸ÌŸúÇ€aræ´tûlÚÿé:¿ø¿¨zÚß; G§þ MŸ�BßIþÄ1\àÂë3üÇ’ãEª™ñ°cÀprê?Òë) ÿñòÇp�Ë�“wѸÕE´¥¨†Ú:®é�ô´eÀ³ àY}àXð €:¸Ùž}½½ðái …ŒãæÑ‘:*øì# }½ÿ7C²z$¬pÁLc}­žu¯c ðÇÛï³¾J3æßï0p™³å7´uï<ªúxÕÌþ'Ç€¡,ñ!zë•ôÓ?|¢mIé0 ŒÉ¢§VÒº#UÔÜÖ¡?JIЖeÏ2€gõ�cÀ3êàf{öõþÔ‚.a„ ÇÇíâ(˜‹.”$Õn'êhÖ/—ÊóéØî7iûêghÕìû¬a‚­,ò•%sî¥Ùo>H±«þÑ/`˜üѯè½]Óéä¦éT;÷QÇ€á|Ò÷éÝ9ñôë¹ëiàÄÀÃèÅ´<ï5´´ë�¸Ú² àYð¬>p,x@ÜlϾޠšàC0B4ä�M%:öDháBö ¢#�•Î;\àEyŠÄ�-3hcÚ£”žx§c¨¨,˜ò9¿€�ËÛÆÓžO’èÒk¿p *“¾G³_ g^]Gƒ&9 <²aÔÂÔ–{žª®\Õ1è ЖeÏ2€gõ�cÀ3êàf{öõÕ‚=Ä FíºÓ9P0#\øl6Q].ѵ6ýBÝsåòYmLjÝãiík#(-v€cˆ`.i“n¡7’î ?ÆÞh9¾pÒJzkxWÀ0}Ó´aó$*[ôM3ÇÑs¯¾Oƒ'Þªräü|JÝq–ÎÖ¶è�ô5hË2€gÀ³úÀ± àup³=ûz‹j‚Áà`€ Ž>Z¸Àu8ˆà@"Ìp�§Dìz�¶.C+_ºÇ*iSÑ[)Cé ¤)éO¿ÿà_´0!)í»ôzâí´(öFz=á¶® aù'/Ðá¥O:† \vÎK_]N_I 0 Ÿ»Ÿfl.¥’êð¦|w@[–<ËžÕŽeϨƒ›íÙ×{T|:`x4�Jp ÌÅ.ð¨‡á)e…›hÿædúxÑ(Z2e�c˜`/oN½‹þð‡¯Ò´7‡Ñ¤#»F*˜ËïŽqyqÕ¿8 û_üM~u1Ý;Õ9\àrß̽”¸±„Žž¿¢?- -ËžeÏêÇ2€gÔÁÍöìëMª >}ðz ¼n/ÎÈ‹4†.𺠼ø#/F¸P_]¢M‰Øµöú e¸c�`/‹bo¢Ô¿H³^ý%¥}‡&¾÷ÏŽ!‚Qâ2~A¯n|–^_ñ¯³æ'–s“Þÿ íJùAWÀP�üš™ò=8c»c¸Àåîi¹4~Ý)Ê-­ÓŸˆFЖeÏ2€gõ�cÀ3êàf{öõ.ÕD䇠=\à� œs1‡ ¼ÓDtt´ÑŲ<:’•J[–=A˧ q ìeaü­ôÊK_¢é †Òä¥? ñXs™±þÑ[럥Ì5OÓž·ÿ�NÍþËh…7^K_IXEŸ‹ÙDC¦¼G¯Ïúßt2q$¥¦¼B?œõ'Çp�Ëà„·ºˆ¶×êÏD;øB#x–<«ËžP7Û³¯·©&">Ûˆª7�‰':øÑÎÎ�‚¹ì¹‡èÄ3DéDMÅú…‚ÓÚTKŸ[Oy›iýÂGBž1oêçiÎܯRò¢¿£IïþÈ1T˜¼î—ôZÆXZ½æIÚºä—Tðò¨"ù»–`Á^>šòkºmü§–á¦ñÛ´b>f”AqÙôÔŠBZ_p‰Ú:®éÏ x|¡‘<ËžÕŽeÏýCzz:�;¶«ðïfµb&##ÃrûµRRR�›ïÃ~-ûý;Ï?›çó'OžÔs†_3øyu�ûå¶�îÃ|=Õq³=ûzŸj¢ä‡`[mïÂ…æýBÁ©«*¦¢}é”ýÁs´fî0Ç Á^ÞœÔ9%â¥Ô{)áíï9N‰˜½î J[óïôñ’Çh÷+Ñé#Ä@åò¼ÇiïÛÉôµ¸MŽ¡‚¹ ŒÉ¢Ñ‹ hÍ¡‹ÔÜÖ¡?3àEð…Fð,xV8–<‡ïl–¾·BÛF½§»œÙ;ìf¸ãÎä矾«Ÿ��­uº øws¨àt-óq¾Žq{îx›¯Å˜;ôÝ�7_—£SGž��� 'l00?GÆ~M;=¹Õp³=ûz£j¢Ä‡ ‡ UëˆN�'Ú?Ü9L°—}C}Ÿ ÏU®"j­Ð/˜ö¶fª,Í¥ÃÛçÒæôÑôδ/;† ö²`ò@š;û/µ)ñïüÐ2%bʇ�Ѽ5�ÓêôÿI[_û!Ìþ>]œê ø•äZ¨Ð°rÉXDË3·ÐïVäÑÈyµ NÁ‚¹ŒZxXû ohiן!ð:øB#x–<«ËžÃcÅ� t[lÝ>)»«,Ý×ý÷t;æ»>nt¬�Îs°¿Îº–ù8‡  0N×âßù8ÓÝyãºüøœî—ër8a¾o®ËÅ8f\ß|ܸÿ>oÞ¼®ºF}óïFcÆ~æë™ï��uÌÇ�cÆëƝ9kÖ,Ë9¾-Ÿ‹&ÜlϾީšxòC�Ã�pÃ…¼aa… <%¢äè:Ú»!ž>˜÷}J›t‹c˜`)17hS"fÏýMýã·)öÝk¡Â„¥Ùk~NiKÿ…>~ýaÊ}åTöâ÷œC{áPáõ_SÃêXjÚ¶ˆÎìÝBf¡˜ŒSZ`pçä]Ža‚V~ïpÌWG–@%ð…Fð,xV8–�tÏ«VÒ�¤ÝŽßG#Yx­±Ôgõ{íì°�[.F§—1F4ð_ö�¾qÌ ûµøvNÇ�ηӵøw£#ÝÝyãºÆcsÂÞçŸ�Ûóã3~æãöëð1¾½ýÝuîÍ×âÇË�‘1®güìô8ÌØë›×7^s½hÁÍöìë­ª‰'>9à�€Ã‘à&Ø ‡Fp(Á#º¡¶²�Žä, Ì¥¿¤ô™w;‡ ¶ÂS"^�9˜^zí^JX2‚&¼?Ц|ð?éõå?¡÷ÞümõèØ¬ÐF+Ô¼øºüÆjx?žš³–Pëñmt¡ì4­;\©m5ùXÚQíƒÔéÖ©ð¥¤[c³,ÇïˆËÖ¦LõÀWÀ³ àY}àXÒ=�µÏò=ÔÍÂ#{ ¸ÃntþÍpGÖ©skîäÚ t-ûq¾wŽ�®eÜÓÝyãºÜ™ô˜ìÁ€½cîÇì×0ÿÎ÷Ë×àb<.ûµøu4¦bÇíuŒÇÁõŒëq Tßüš·�&ÜlϾޫšDå‡ ¯™payèá¯Ép`DÈáO‰(8º†6®û -›ÿ]úcü@Ç0Á^$|Žþðò_Ñô7 „w~H/¿ûJOû±6ZaßÜÐÙF+ÔÌx˜ê=IWÖ&Só®eZ¨Ð^UBµMm´©°šfl.ÕÖPà°ÀéƒÔ©pÁaß–w¬ºrU¦¤ý~¿ïƒþ–‰;éÞ™{4( ¾¸ÊžeÏêÇ2�î™Gðú`Nß_#YLØI1™§õ{ ˜;½\ø˜Ñqv"صÌÇ�ß�®Åj£#ßÝyã:|ÌèèÛ1×guòíÇã˜ýößíØ¯NØÀÏÃxÎÁê¡‹qÝhÃÍöìëѪIT|r¸À 3ò�¼P£S `..ðÂ�¼$/É»MàJk=³…>þ4ŽÒßú-œñemºƒS˜`)<%"ùšýÊ×iæ¢iá[k£²^ý*|éûTÕÍh…š—~Luo=MW2¦k¡ÂÕYÔQ[®=&^'�·”œ»­ŒžH?F÷ÍÜëøÁéTxÚOŸàÑëŽT…¼p¾Ô¨ËžeÏêÇ2€çÐ)©n¦ÏÙFäráuŠ«šôZ¡atØípGÖ0æû0ÿÌu̯»ñ:1öú Ÿw d¢7Û³¯‡«&ýò!ØXHTþfèáBÖ@¢C� :®µÓ¹Ë§(ï³?ч;¦Ò[+~F¯¾ò z=á6ç0ÁVÆ  ”™Aó^¹—Ò_û6}üÚèàœPù´àÛKÖÌþ§ÎPᣗ¨y÷»tõdu4\Òi;p,x�Å{ÎÓ IÙÚw`.4¼±ëœ~6tÌvî¼Ú;ù wˆ¹#Ìõùg£Ø�nûqî8ØÏÙ;ÔÁÎó9.ü˜�:ÞÆm�`Ä©“ïÔ™7Žq1?fÆ|M;\×ü¸�ëšïÃ~ÆãàcÆíº ŒûˆFÜlϾ¯šôɇ .C´{ˆs `.F¸P’LT»�¨£Y¿P'õÍ5Tx!�¶�\CËwÏ Wßÿ%½4ﯵõ84p ìå�„ÛhÑŒ!´âåûhëœok£œÂ£p¨P¿ä?©ñã—©yÏjºz&�®5Z§k´u\£¼²zz=ë�[]DÃçî׆u9 öÂCÌx�…ñëNÑò¼ tôüýª‘ÿÙ©ËžeÏêÇ2€çðáü]šKoþм ‡ö$Zp³=ûzÀjâÊ‹Ö�Ot6•èØ¡… ÙƒˆŽp,x <�äˆÜlϾ±šDäE3Â…‚ÑD»îtÌÅ>›MT—Kt­�j›ª¨ bm=±šÒ÷̤9[~CÖþ”âßù¡¶ ãÜÙ©-Ðè&ØKÚ¤›iUÂikÂÝT”ð-ªJzÐ?THùÕ¯øojü$•Zö¯í šëõ'ä�4à<ò€§6ðÐ.§ Á©ðÔ éÀ -�|à©} þ³S8–<ËžÕŽeϨƒ›íÙ×CV“°_´km�G -\à:DœõuìkvRYõqÊ9ó±6ZaAV,MþèW4þÃÕ¶ŽLxû{ÚV’¯NÿsmkI§0Á^Þ™4�>‰L'�J°„ —ç=N +'t† 3©íÜ1ºÖÚ¨?gxȯ‘Àk%ðbŒ¼(£SˆàTx‹^›�×hàµx1ÈhÿÙ©ËžeÏêÇ2€gÔÁÍöìë1«I·/š9\àÑ<*Á)P0=\¨>=‹Žœ^A› Wj£f}:N Œûî�;§DÌýÍ›úyÇ Á^Ò&ÞHkcÑÎø/ÓÑ)ߤ ‰ß¦êä×C…OçSë‘O¨½â„ï¡·êO"0¼›ïêÀ»;ðö‘¼�¤SˆàTGÛM‚w•ØRT£m_­à?;õ�cÀ³ àY}àXð €:¸Ùž}=hÅൌ ö¬»ˆöïü�áõøg^œ‘i !\hÉù+*=ðkÊ:Kæ½H©ÛGq™£-ÁÂø~BñËþ�¦/J¯Ìú2-Œ¿Õ1L°—e17ÓúI_ =“¿BʼnPÍëOPÃêXjÚ¶ˆZ ¶P{e±ï1‡6z êÊUZ_p‰fl.¥Ñ‹ èîi¹Ž!‚S’´[» ß–¯Á×òøÏN}àXð,xV8–< n¶g_¯Z!8LȾÝdÝJ”÷w�;A˜�ÛJåŸn§CÛî§�;I‹·?C3?yÒ*èeâ{ÿLIiߥ—Rï¥Ô¿ò”ˆ÷b?G['‰½üU¼ójÎZB­Ç·Q{UIÈ¡ã x´�:àÑ< Á)Dp*<º�G9ðhõÀ£¼þ³S8–<ËžÕŽeϨƒ›íÙ×ÓVˆߣâ-_¤”̇(aí?ÑÜŒÐ~7‡ MÛhDz>ù½ÿÉ(eãÏ).ãgŽÁ—Iïþˆ¦½9ŒæÌý*ÍOä$ØË☛h]â—i÷k?¦SÆPá�:C…0áux}^'�×KàuœB§Âë1𺠼>¯Ó ê;øÏN}àXð,xV8–< n¶g¥Â†âíß IkGY‚Xßïé§´�¾KÓ2þÙrÎ^~ÿÁ¿Ðä¥? óï§W^úRÈS"–O¾ƒ6§>Dùü–.žø”::Â_ã€wràxgÞá�·�t œ ï Á;IðŽ¼³ï0!üg§>p,x–<«ËžP7Û³RaCÊúGC„ñ>êp̘ñšõê7hþ´;é�17:† æ’s­™õ-Ê~o�Ê_EõÕá�Xhë¸Fùç(-÷<½ða±, ˜°Ó1H°—�1Y4"å€v;¾=_‡¯'üg§>p,x–<«ËžP7Û³RaÔ~BSÒ¿Oó’ï …ñ·h;AðïF¸0iÅHš¾ðoéõ—þ’ÞúÿÛ»Ÿ»Î2Žã UlÓ ]]˜… ®Ä… ·.] .„vÚ¤Ú‚U©õRi-M‰“k¨´•¨ F,Á?EojMÿd¬ŽÔL**iÒµ­ }íïä>É;oÞ{çNsŽsÏó|?ð2sÏ9÷ÜÉüæ=ç¼OΟ½×T‹ e;xÇ®ôãŸNϽ+½²ñ“ôæ¿^Úìt¦�Î8Й¿ÿDS0¨ʦ„ :ÓAg<è̇ȅ…vvþ‘q ä9ûGÆ1�3àG—ýÙU±áë�ìNû—6ß°qÿÒ;ÒwÝ�,]µiú¤öØ—?�~þØçÒú±Ó¹3kã5ÏN÷FÐ=t¯„O~ó7Í%µBB­}ôÞçš{3è ºWƒ.­Àtììü#ãÈ9rö�Œc gÀ�.û³«bãûn¬¹µ2í­¦Ä¾±;ýjõ‹é�/Nÿ|íìxM³ÑÓôT=ÝA7cÔMkE„ZûÐ=Ç›§Iè©zº„n‰ícgçÇ@Î1�³d9~tÙŸ]í»¡ZTØ?þ:\¾1=¹ò™´ö³¯¦3/?µ­K"^}ãÍtdý\úÒÑ?7��Ôc$kE„Z»éîgÒgWÖ›÷>ùÒùæñ•h;;ÿÈ8rŽ�œý#ãÈð£ËþìªØð½ÁÍ—ÔÝùÞtþìïÆKmMÅ�m ³T$P± VD¨5!TŒÐÙ:ëAE t‡��d9Ç@Îþ‘q ä øÑevUløËï�¤á¾ë6¹Db]¾ û#è> º¬A—7ÔŠµ¦Ë&tù„îÏ û4è² ü±³ó�Œc çÈÙ?2Ž�œ?ºìÏ®Š ò£Ÿ>‘¾¶çýé¡[Þ™î»í¦ôãßÏIÍ õD=ÙA7bÔ kE„ZÓ�?ñÀZóD‰ÇOüµ¹$v;;ÿÈ8rŽ�œý#ãÈð£ËþìªØðÔ©¿¥]·m~úÃÕK£ô©‡_l!©GIæó&5=šr÷à…ô…ïŸJÃãg›GWb>±³ó�Œc çÈÙ?2Ž�œwÆh4J M[^^O½`uuõâ<µáp8ž“ÒÆÆÆ¦yƒÁ`<ç‚iïÕ÷ùkÑgëg™wú·`k]ögWņ� ^¨¦5 Tˆøü'ÓÃOŸIk¯¼žþýŸÿŽ×ˆyÇÎÎ?2Ž�œc gÿÈ8rÞ‹‹‹ù*¨ˆ V,È鵦[�–l=ÓÞ+ó\lÐÏXNrå¿ u]ögWņYž¡K't ….¥Ð%º´ýÅÎÎ?2Ž�œc gÿÈ8rÞ¾7þ~:�|v˜^úõ�æû·#/6ä4ø·â€ÑkM¯ÍËM{¯ÌZlÐ{4¸W³åUP³iú^_ËåŒMWÛjýùk5+¦Økû\l­ËþìªØpó}ÏW *Bè&�º$|agçÇ@Î1�³d9oÏ©ßI+{®I+{w5íà[íäs—î+7+ À5€¶B€Q"?sAôZÓkórÓÞ+Ø—E�Z±¡¶ øóŸ5­eób€"&©­_½ÏhÝš&VŒÀÖºìÏ®Š GÖÏ¥ëöÛTh¸þöQó¤øÄÎÎ?2Ž�œc gÿÈ8†è9¿¼öxzôî÷mz^íÐ�ïI¿ýåýãO½Dƒr ¤mÀ?­`�ÏÓ`\ï›õ½2k±¡v&�¦Ùà_Ê×ùzÊŸCßÛϪyµõ—ņråò¨ë²?»*6ˆ þʳé]·ü"}ðžãœã Æ?2Ž�œc gÿÈ8†è9÷Þ�T‹]´ƒw\;þÔÍT°�vmðoñüûM¾ü´÷ʬÅ£¾-¯uäŸ]¾Î×S+zÔäëÏN¡ØðötÙŸÝ ;»ÈÙ?2Ž�œc gÿÈ8†è9ëlƒoÝþîjq Ív`éªôÌ‘[ÇŸzapn4ȶA·¾j �Ó@[y5}ŸÂóAþ´÷нߔ¯Kšo?g­Ø`óìÌ£éöï™&_YlÐtû<­kÚωKºìÏÐkäìÇ@Î1�³d9ÏîµóJ+{®¾¬� û6üãÕSã¥fch5p›|žZ>È·AÞrÓÞ+z�ϯ���Ï×çI­Ø fËåó¬øP®ÃLšgÓôþ|ú7•EÔuÙŸ)6 ×ÈÙ?2Ž�œc gÿÈ8rÞž?_I÷^›VÆM…†õ§Ï�£,>`>tÙŸ)6 ×ÈÙ?2Ž�œc gÿÈ8rÞ>�á°~ìÁ¦m÷Œ/(6̧.û3Åô9ûGÆ1�s äìÇ@΀]ögŠ è5rö�Œc çÈÙ?2Ž�œ?ºìÏÐkäìÇ@Î1�³d9~tÙŸ)6 ×ÈÙ?2Ž�œc gÿÈ8rüè²?Sl@¯‘³d9Ç@Îþ‘q ä øÑe¦Ø€^#gÿÈ8rŽ�œý#ãÈð£ËþL±½FÎþ‘q ä9ûGÆ1�3àG—ý™bz�œý#ãÈ9rö�Œc gÀ�.û3Åô9ûGÆ1�s äìÇ@Î1�s ]æ<·Å†ÕÕÕ´°°Ð´åååñÔÙÑ9b gÿÈ8rŽ�œý#ãÈ9rŽ¡Ëœç¶Ø "ÃÆÆFó½Š *>l�#rö�Œc çÈÙ?2Ž�œc çºÌy.‹ *,äg3èõ`0¿š �#rö�Œc çÈÙ?2Ž�œc çºÌyn‹ yqa4mûR :G äìÇ@Î1�³d9Ç@Î1t™3Åô9ûGÆ1�s äìÇ@Î1�s ]æìö2ŠÓ§O7¿8�F£Ñh4�F£Ñh—7�›»2—ÅÝòJo vÆ\d8^|ôåvÏj;gn‹  Ÿ(6€VQl­¢ØZE±´ŠbhÅÐ*Š  U.‹ Ãá0-,,¤���ñx3 šŒÕ”7üZ]]mrÖWø£í´õeröǶեÅÅE2wbyy¹šå¤éè'ehyªÿæì¸{4�§ O,Ûå;Š ¾ig§F_ö)?˜Ñ6Ûò¶Æ¾ºßtàjYÖL+B òŒÕl€©þlÙæôÏ´ü´o¶yÚ^Ûßýº,?cYæy¶½Ÿ¦Ø€^Ë7€ð%ßñQlðËvjöµ­Óö0?”«QÎùªòæÆ|°>\ºÒUì¼r j™æÇÚå2è—Iù•Ûh‹)ëï›õ‹rž”™ú³æ)Û6÷ÓÐ[Ó: úO;±²1õ§ÜV+g¶Ý¾(ScSÔ ßÊ‹²® `Ð/eÁ_9>|xÓþÙÇeýTö]Q–åöYmÏóטoêÇÓŽ£5O­íý4Åô’þèxÆQèÀ¨Ø.>•™êµíŸ·:øÁ|SveÿµluÆvÛålÿ³9i;]¬¢?ÊüÔwó§”}\Ë—Ë`>©ÿZžDYÚß@›ûiwÅuý‚¬mõ‹Eÿh˜g¬ÆÁªoêÇ´úTögröC.y¶¶�Î3¿’ÿ-ÁγÁ‡5;PUÖùt5úv¿åý¹VT ØÐO:¾Êû©m“óijVT(û6™Ï?e”g¦¦ÜËéùXªÍý´Û3ÀΠØZE±´ŠbhÅÐ*Š  U@«(6€VQl­¢ØZ”Òÿ0:bÆÛ ¶ÜIEND®B`‚sogou-srpc-ac65014/docs/images/benchmark4.png000066400000000000000000000776711525217424400211110ustar00rootroot00000000000000‰PNG  IHDR*e´BösRGB®ÎégAMA±� üa pHYsÃÃÇo¨dNIDATx^íÝ ˜œåyßÿØ!Ìe»4¥‰Û¸±[;-mœš$NB\üihâ8¸uþE—ýwí«NKšÚXBÂZˆƒ, Y€@„´`€A Ž Ðê„$$ÁJ Y�ÏHâþïïÝyVϼûÌì»»3»3÷~?×u_Ò¾3óÎa÷~fî{žç}Åjˆf¨)š  ¦h6€š¢ÙjŠf¨)š  ¦h6€š¢ÙjŠf¨)š  ¦h6€š¢ÙjŠf¨)š €~Y¹r¥ 6¬,Z[[K—¢™¤~—S§N-]:tœwÞyÙsÿÎw¾SÚz‹f _ÔXȨ4šSªÙÐÒÒRºthˆ_ƒzò…r=ÝÞ‹¾<ßJ ƒ8TŒkö@^‘¿“|ñ_­‘¥è‹¸YP©1ëíß\¼=ˆg6è5há¾õw@­ÐlôKoš ñ·Í�>úhYa’q³!.Ôâ¢L¡o¼cñLý?¾?ý_×�÷—¢ëÄû§ ä"·÷"¼EŸoÜhÐï(þ}굋ºþ}(Âí´ÿ°M¿ë°]ÿêïP÷Ñ[ú› ûÌ7À*éíß\¸®¢Q4âc4?š €~ék³AâUEY ë…íùb7.2ã‚2¿½È·Ò)aоîÃði6Ä¿/Ejæ‚Ä ñï[ªý�Ä3M´Câë«IP qc£È>ûò7®¯h�ø˜Í�f _úÓlˆoµÕš ùÂ6ˆ¿ai_ÄûÎ?ÞJt½ü”~ýœ¿}|ðAý_Ï_Eª~Vqï#UìÆÏ1V«û�…ëå_ÿ”¸HÏ7bº�p½¸Q$ÕþNâËÂßXj[¨Q^‹üc«¤/sáúŠ þ½è÷–××߯^ïø1ê6Úä÷GêqÐ4ý7 RE_µ"2žÙßV× ÛóÅn|™B4u>Þ–Ÿ¢ßqq¦¢S�«Ú7Öñ·Û©ÐåAüØãûQè~T°†ŸóÅ^üãµõµ¼ÿXØžýSâ™ñýåéu ×SÄÕþNR�…üñ!ÔäèÏï=~«5L‚¾þÍÅ· âßKþõ®Õï7„þ¦Ãc¥Ù¨'š €~Qñ ”|Á*•ŠÈ¸°Ž ©V|Å÷§‚Jâë+ú#¿¯©b6.8õXÂåÚGê[òJûVèyÅû‹o'ñë"Xëû�…íù×?%Þ�J×­ôwÏ8P„ùí!´ŸžCJ\x¹½®ßoQ©ÛÄûŠ_ïþþ~õœt½Vqó¡ÒïZ@­ÐlôK\ü狉‹ÈT¨XÊw•Š/}‹˜á[Ý|‘Õ_Ú_¥3RÄß$ÇËòÏ!~]ÂeùÇ©ë¨ ¡ÄEa¼´!Þ¦Â×ãþƒp�øõ¯$ÞgþqäUºnüw¶ëqÅM€üÙty¥¿¯"³=÷p»ü}T’-‹JÝ&ÞWüz÷ç÷«¿—X¼„%ÿ; ÛÔ Í@¿ÄE�þŸW© ‘/¢$.š*E\Læ¯_+j,¤xÌ==·©¦H¾à â á9ª¨Ûâ"²÷½ž„ë*ÂkSI¥ëöô\ÔdŠ�7Ó~âÂã‡îSû‰gTÙù&Roôæo.¼.q¢§ßK-~¿Õ.Ó>ôZÆ�-?‹€Þ¢Ùè—žš hNáwÚ›fCOTÔæ¿E¯åþ{+nTÄE;è?š €~¡ÙàSø�Ö£ oúÕtP±Ÿú& Ä3Gë1àÍ@¿Ä͆4šS<Õ>Ä`Î<¨§øXýYBÒh6ú%U ÒlhN©ß¥f xŽ¡Ù,¡ öh6€š¢ÙjŠf¨)š  ¦h6€š¢ÙjŠf¨)š  ¦h6€š¢ÙjŠf¨)š  ¦h6€š¢ÙjªÇfðaú¢µµµ´Õløðá]ÛÛÚÚJ[ë¿4¶ªÍ†I“&u5ÚÛÛ³Â_´M—‰j H½·€ÆWµÙ ¢_!j6„¢_ÿª èçpy=·€Æ×ã2 úa9C�/þ[ZZ²æ@½·€ÆWµÙ ‚?3Aÿ†¥ ÍÐlxóÍ7í•W^!‚ ‚ ‚ ¢‡P ]KU› ù"_M€Ð ÈoWs ÞÛ{C/ÈgÀòð…œü¨u>Wm6¨ÈŸ3gNé§Î3S¨è× ‡Ôë½½7ø?ÈgÀòð…œüÐfƒ ýp¼E8X¤ÄÛãYõÞ^àù øA>¾�Ó€Úlhf |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�¯²ÖÖV6lXúlÒ¤I]—)ÚÚÚJ—˜Í™3§ì2ý«vÛ–––²ŸE×É>|¸µ··—~jzìzä3àù øBN~Ðl(ÈãÀwdç/íÀÊûìÀŠ{²ÿ÷… ù¸ÈWƒ Ðÿã‚Z¶®«Û¨)¡F@,þ¹Úm¥š zùJ@³apñAðƒ||!§?h6ämà;øÜC¶uÜÙ¶qÜ—þ±\5·tiqùfC,n*ÀÃLˆjM€j·•Þ4t;]¦ˆo_¦íú9ž¥®«Ã6EJþ>âŸC%Þ�.§Ù0xø øA>¾�Ó€4 jô�ïàêGlû„3lë…¿W÷Ø6þ˶ñm¥{>ºŒ"4D�‚PdÇôMÿ%—\’¼,¨v[éÒ›fCx\º}\àÇ—I¥ëj{þ¾b•¯k<³A�/4Ph6 .>È~�Ï€/ä4à͆‚}àÛqõ×’��zŶKÿ¤tÏG©xoš ºM©u³!ø_'Y꺩ǢÇox<ºN¾y7òûÑc§Ù0xø øA>¾�Ó€4 jô�O3 ¶]ò‡ÉÆ@Ícì©¶÷‘É¥{.§:Ø©â?à¡��Åׯv[ÌfC%z<ºMx\4d?ÈgÀrðƒfCAž¾#Û7ÚÖq_ìÖDÐqoÝPºV1*šóKB±­b:|ó/¡Ý&_ÄÇ�€j·Ý>.Öó?º�ÔÒÑeùfCêºq¡'º}ØGÜlí'ÜŸö�z¼|�ü Ÿ_ÈiÀš yø~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ ¾ÊZZZlذa]ÑÖÖVºä(momm-ýÔy›øzù}T»®è:yÇ·öööÒO�C�]Ï�ƒ|ü Ÿ_ÈiÀš 1ðU7æÌ™Ó­°Ö¶I“&eÍ€ ß@Èÿ7ò—I£5ôüôÈ~�Ï€/ä4à͆‚~àÛ<ÛlñÉfOvü ê‹N2{sréŽË›*°ãB[x(êãB=ß@Èÿ,}i6„û•ø:ùËR×�_ Ç¯Ëjˆ®“oÄ͆ü~ôØó×Çà⃠àù øBN~Ðl(¨á¾eŸK7êm'”î¸zã@ÿÅzˆP|Wº�èÿ¡`Ï_&ÚO^ª�ä/K]7Õl¨D�'<¡ÙÐ\ø øA>¾�Ó€4 jø�O3 —n Ô:ž:ÆìÕ¥;.o¨Ð…{ªx×,EOÍÝ.̈×ò?ñmTøÇ×ÑezÈ~�Ï€/ä4àÇ€6b*ôTðI<Ý^�€�ÚÞ |€ä3àù øBN~ Z³AÅ¿š¢â_M€@?‡fD=·÷àù øA>¾�Ó€ƒÖlÐr† _ü‡ƒÖ{{o0ð~�Ï€ä3à 9 ø1(͆xYƒ4K³¡¿ u©¼ ‚ ‚ ÂgÔR¡fƒŠÿ¸ØOý¬æ@½·÷F­_(ƒ‡|ü Ÿ_ÈiÀ�o6Ì™3'›YÓ,‡Ôë½½7ø?ÈgÀòð…œüðfƒ j8ä…ÓR*âYõÞ^àù øA>¾�Ó€ÞlhV |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòðaí;ÏÙ¤'εÑsþ«M|ìﳟ4§zå3Í �|ü Ÿ�æ§Bäüûþƾ÷ó¿èŠÑ÷Í^Ø´´t Í¢žùL³@Ã#Ÿ?Èg qmÝûË,T|¼²y¥=ýú/lÑkØÃ/Ͳ¹/Ì´ÛW\i­K/ë(DÎ*+LŽÆ™‰mA4tÜ“ØÖWü⻥‘¡ïh6 A­­­6lذ,ôÿؤI“º.S´µµ•.1›3gNÙeú9Ví¶---e?‹®Ó“áÇ[{{{é§ê´¿p]ÝWx=ݾÒ}¹-ù øA>õsèðÁ¬YðöÎõYÃ`õÆEYÃàɵ÷d ƒŸ?{]Ö0˜¶pŒ]»`dVLŒ{ø[vÞ=™,6‚º¡q¡¿h6 1*žã"_ ‚@ÿWS »n£¦„ŠòXüsµÛJÑfƒö71zÓlˆéñ†ýä÷™××ûÀÀ!Ÿ?Èg º=wf ƒ Û^É+ß|2k<ºæö¬apÇ3“³†�š×âË´ïÐ4�7aŸyùû�3ì/ü?Þo¼=lÓ>‚xŸW\qEÙeúµæº£8ü Ÿ1èsZ~9‚>¿å—#¨a0é‰ÿ“5 .¸ÿ¿%?ðT¨a¡Ç¡†×­Ë'd�S�Y�=‡åo<ž=§'Úï¶QsÊ×x�ºïköì[ J¯€f¡c3\üÓÓìê±'ÚÔѲ«/ú¨�ýéŸÖ$Ÿi64‰g6ó¯O|=CqøA>£„å›wmÈŠk}�—#ÜûÜ´¬Ÿ¾è¢†ZŽ ÇpÙ£ßÉ“–Jè1Þ¹òšì1?Ñ~Wö´´BÏé­ë²çxàоҳî½&zÞ?¸÷+Ù7 4€æ´qÝ|»iô‡í†üJWÜôÃãí�ç–®Ñw4‰¦Ä Ô”·1sÿ6{sÌSQ þ|A¨øÎ7t›Rí¶¡(¯Å2 Ý>4$_´Ç?Çû‰ÿ¯Ãc�-¿Ÿð¸â핇 aŠÔõ%<ÿüs@1'€ä3ʾ÷v'—#<öòÝ–#(T¨_üÐ7’Ÿ¥*FÝwVö8&>6<{L3–\’=ÆûVOÏ³Ö¨ç°æ—˳ç´e÷Æì9&r¨îð¡ý¶këún±sËÚ¬ÐOÅ+ËZ“ñ̼KmÅ£c»EÛÝçØü;¾Ý-š~¦Í�zz·¸kâ)vûåŸÊâ¦Q*k4„øÙ„Ï–žAßÑl$ê§ÞdêzóJQá ñPdÇB¡ÒA|ýj·•PlÇzº�T*ò%_ÐÇ?ÇûÉï3/¿Ÿð¸âí©Ç¡ŸãçPéú¢û×ãPs¢ÚcAd?ÈgôFXŽðê»/dŵ í°á¡oÎ ñ[ž¾"+Ìe9‚¾äÑã˜<ÿÿf�+,GÐãÕãÖsËô¼ôüvíßVzÆÍ‡œF½¥ uŦ×Ú’…úºU³“…úªÇÇ' õ…÷œ›,Ôm=+Y¨ßsÕ©]…z³.>9Y´7kÜ8ò˜Òo ïh6 Í41篓oRµMéSG\T(‡éý¢¢84T@« ´=Óº�®‹ íj·Ý>näò��T‘ä úøçx?ù}æévá5ÑõÂ}ÄûK=ý¬í¢ÿëù¦®/áºáúè>È~�ÏCË‘÷gÅt~9‚u÷e…w~9„yÿ;+Ôê3R¥Ðc˦¶�Îc~9‚– è9õw9B³#§ÛÞ]›’…úæ×—$ õWŸ»+Y¨¯^09Y¨/™;"Y¨?6ëìd¡~ï5_Lê·Žûd²à%/f_ñéÒ_QßÑlhz™xÓÕq4}®·T(«0VÄ�‰/‹‹gÑuãËâÂ_ªÝVTô‡Ë*Ý¡I.�‹ö¾6òûÌÓöø¹…}Äû‹ÿ?ŽüsJ]?Ðmò¯Šáƒ àùÜ|ÂrÕ*®Ud«ØËT„«WQ®â\…ú`/GP³B�CÍ =¦°AÍ =f5;ôÂr5CôÕA1*Lõ ïÌ1·»'}>ûÙ›ý{¶$ õw7®*+ÐCh�{ªPqñõÉBýéG' õ'nÿf²PW¤ uÅô‘Ç$ Fbpâ¦QÇ%O³Ç&ù{U¤þËnIþýèï*õ÷¶aÍÃɿϭ›žOþ=¿ºêkóѲÇ?s̉Yã©¿h64‰¥ëÉš Z¡Ðÿ¾zéR4ƒžfX 2ŠÀòyphš¾Šéx9‚B…w~9‚¦ÿ«P×r€T!?P‘_Ž Ç§Ç/GPèùxXŽÐLT¸Ì¼àø²â¤µ£8‰(÷Þ�ÝÉÂfûæ5ÝŠ … ¤Tá´féôd¡¥HfЦ�‘,æ~6ásÉP…aü\ˆÁ�ÔïIqßµ§%·šI‘ú[PC'õ·óÂÂ)É¿·õÏß›üû|gÃòäß³fŽx ÜÕ1¦Ÿÿ¡lFC- B³¡‰èMT�xE_f4`ðh–ƒf? o(N?Èç¾ Ëôþ¯âZßÈ«ÐË´\R…¸¾ÁWaÞ(Ë4ÃA�C�),GÐ�õ˜53BÏ!,GÐ�õ5“'MaZ}üí|(Ìšö05¾õ‡åß‚vňÄ6bÀCÇHêZÂ�/Òónùz·"]±xÎ÷Ê ôÏÍŸXV ‡Ð±òEºB[q�bÏŽ7K…hµ~�¦Ù á‘Ï€C9Ÿµ¦_Åt~9‚Ž â;¿AÇ P¡ž*â*òËtl=Æür‹AωåCEšŠµ0�?žŽV¯BQc<%?|³ËE'%‹ÔþƴĶf ½6ù"]¡¥"q�âá_éV¤+tv€|‘®ÐA ã=DûŠYÝŠôù"=Ä‘#‡J@mÐl(ˆâðƒ|ühö|Ë^ߺ&+®uVÛ*¼*Äuöæ�²AggÐãÐÙô¸Âr„¹/Ì̳Îî ç ç£ÐóÓY P[á@}Z7ŠÈPhê›b¢ñÁöB1NQ× Gº¿qÄ»é 59â=„NËè!žºó»ÝŠôq�Ç[íóÊ ôZ¶‘/Ò:!€r4 ¢8ü Ÿ?!ŸULç—#,zí�¬ðÎ/G˜øØð¬P×ñ’R…ü@E¼A¡Ç˜_ްòÍ'³çÄr„Ú Œ�3 o U܆£óëàm¡HÇ Ð·à*²ýû¡¦×ÇßÒ‡â>¬m�§È¯Yr£Íüaù1ju@9ƒ‡fCA'€ä3ÐüTOzâ\=ç¿vðŸýÜWa9ÂÛ;×gûY½qQVl‡å?öº¬Ÿ¶pLV˜7Âr�†Z�áŠ_|7{La9‚«³N‰­ç–#è¹é9:|°ô¬QÔÁ}Û³ÁÎ-k»ŠãpÞ}ΡˆEµ lÚñ)ùâ"ºÑB =Æ0­_ Žð\ÂQëÕÑó�§æ‡oøÕ@©…zPÀà¡ÙPÅ àù 47Ï:‹R\|�ºÿ,[òú#Ý–#<ºæö¬øV!–#\óäyY¡~áƒg—íc #¿¡uéeÙãÌ/GxeóÊì9±¡8MiÓÛ7½Ö–Çá|ÿñi5½^Eõ£­ge…¶ŽL�|F�pÀ>-‰ÐãV„�–Nè¹ÅÝ ‚pªºF>â=ïÑ€4 bàü Ÿ�æ6ññ¿Oïßûù™‰mõ 5,Ô0PC 5 j¨Ñ/GP#D ƒ=w–ž b:8]häÏdŸ®PÊSQ­#Þ‡B;4fþð„dqÞo@[Ôs Gé×ÁCƒ œÛ^mÔk2TŽ´Ï{4à͆‚ø?Èg ùè}- PAŸ*úûùåZ*‘ZŽ ¥,GH �7^Åqµ3„S*f�ÿLV|·¶|,Yœ7B¨y¡Ç¨Çw8ÿ¾Nß áùª9¢× >5z‡÷hÀš 1ð~�Ï@sPa¯Ù:¨bÜ ø~ôÿ8FÜû×eËtpF5 t°Æx9‚樆ÁP–?ա޼ f�JOtêLõ>Õa-BËôZ¡ÇÎûŸ>P³%ô|×?oöh¹Ehpf�ÁÃ{4à͆‚ø?Èg q½úî Ù1 tÆTCAñý{þ‹ý #âm£îûš=ûÖ‚Ò^| g2¨vªÃ§ÝÕ h¦3LyLWƒ Ú™ t¼=_!¼¡AðÞΖáïÑ€4 bàü Ÿ�Æ¡åšq Ó.êtŒq!�*R3tàG�=BgYÐÒ‡Üû»ì‘ï4l£¡Ú™ R§:|hú™Y¡}ÏU§vß©â¼Q"Uœ7BT;Õ¡fFè¹U;“A­Nu¤ð øA³¡ >Àòx:ƒNå8}ÑE6bÎ_'› Ín¸÷¹iÙ±޼¸tër*|g^p|YÝ:æD{y錬§:ÔZ|ËÕÎdÐ,§:L�É 4©u¬…Ð Ð1ô µ3 ùñ øA³¡ >Àò:£Îæ ÓBꬩæ‚b¼ÿm½x³½µc]é–ÕÝ9áw’ù #Û1t–5Rg2¨vªC�Õ!̬†Þ£?h6ÄÀøA>õ£†Aê ùPâ±—ïÈÎ ÑMÛ×zé³.>9YØ+¦%¶õ6©¡APíL©S9r¨ôÈôïÑ€4 bàü Ÿ�ÚÒ$tšÉjg�Ð̆©m£³¥Û÷õ¼æ_Å»Šz!ÕH…Ž“NuøhëYYƒà©;¿ÛÕ ÈŸÉ€S�‡÷hÀš 1ð~�Ï@ÿÄg�¸ðÁ³“ÍEþ Õìݵ)kΦŸ_~Æ™cNÌššïÑ€4 b૬¥¥Å† Ömmm¥K:i[kkké§N“&M꺾nä÷5gΜäöø>â}å/“j—k¿ágý;|øðìÿÕè:ííí¥ŸªÓýéºÚw¸ÿ"·­tEo�êÈg ÷jq‰˜–hf�N�¨³JÄ�ƒTÜ5ñ”ìL š‘ ÛjùÂÏ&|Ö¦Ÿÿ!›}ŧi4Nð øA³¡ �ß›ÛXëÓ›lú’·³ÿ÷U\°ç©Y b?.âÕxˆ ú9n*¤öo×uÃíµïx_¡¨yO—‡ýêçJ…¼öŸô¦ÙÄÏQòûÌëË} 8>ÈÅ=ƒÄ¸‡¿Õã$DgDбt <1ÕT¡Ëu0E]¿Ú™ÈgÀrðƒfCAÞ¾[Ÿù¥}dä;þü¶®¸yÙ¦Ò¥½7òt™Šæ¸x®öí|¥}ÅÛU¤«X—Ô¾tY˜IÑÓåa¿z|©ûÕuµEh˜èßx{ؼ=ìKÛ&OžÜu]Ý_jŸyùûˆ÷ߟžG¸N¼=lÓ>‚xŸW\qEÙeúµæ‡7|�*+z ²§3Hh‚f"hF‚f&¤š qh†ƒf:hÆCу,’Ï€/ä4à͆‚}à›½r³�|ábû•ï=Y÷8iÌ"›üäÑo™T@‡âVŠ^ý«WT+âm)ù}ùí*¾+í+4#zº\Â~Có!%_ˆ‡¢]t»ðmÏïGÛô8òûÈÿœïK×Óã”°¿ðÿÔãˆå¯ö™mâë |�Ê©aððK³ �Aâ‰ö»ªžAB[Ô�žñ•ìÌ©¦B›AÇhh_1+;fC_�Ï€/ä4à͆‚}àûÜË’��zÅ £ŽÎP!œš "9_Üæ‹Ü¼JûŠ·kj<þøãÉ}é~UxWº¯p¹h¿zŒÕS¾1ž‡è1¥AØ–ßGü³þ M”ð¸òûÒeo¯ô8ô|Âþ©ëKxMãÛ|�:Ï ¡¥ÕÎ ¡¥á »öo+ݲœÎâ°aÍödîûÙ„Ï%› qèì:3„Î6Q ä3à 9 øA³¡ Fø4Óภ’��ZÇ1ç=e#î{µtÏÕqÑ«Ðõôo%Õöo?§öò=]ö£m¡ÐÏ‹¯/•Šü|1/a[~ùŸóòû Ï#Þžzú9~Ε®/º=5'ª=�ø ƒ¡Hk\óËåÙÁuÇTsA¡ƒ?ÞòôUÏ ±sËZ{aá{hú™vÓ¨ã’M…³.>9;åºU³íà¾í¥=Ôù øBN~Ðl(ÈÓÀ·~ë~ûðÈî� ·aí–ê§&KÉ7DE­ŠÛ˜ŠZ… ÜP ‹ ÝPì¦ö%ñöPPë_m×þ]'.¶‹\ö«Ç›*ºó��T‘/ùb^¶ü>ò?çévz­D×KÝGêqègmý?¼N__ÂuÃõ‡>È`¨P³@Mƒ¢g�xaÓÒä$Þ;°;;ûCÛÝçØìñŸI6BLyŒÍ�zº=3ïR{gÃòÒê‡||!§?h6ämà›±ôm;áü¶l9„B�†ë¾Uº´wâ‚=M…˜® [ÝF…°"ÒoW„‚;¿=.Ôó—ŵT»\—…Ç®íºÈÀ°1œA"ÕXQí j¨Y ¦Áí—*ÙT¡æƒš«o[7=_ÚÃà Ÿ_ÈiÀš 1ðÁÍrÐ쇡ˆ|†µ8ƒÄ»We -{Ðò‡Tc!„ZF¡å={¡òð…œü ÙPàùŒfÕß3Hè�:P£ب7¦š !4{A€Ô� µ¤¢Q‘Ï€/ä4à͆‚ø?Èg4‹ÞœAB�L�AB§˜ÔÁï»ö´g/èÔ• ï97;•¥NiÙ ÈgÀrh~ï½¶ÜvNû†m¹ìK¶cêÏ~®š ùŒFÖÛ3H¨ŸAbÿž-Ö¾b–=qû7í–‹NJ6BÌüá öðŒ¯Ø‹‹¯·][×—öÐ\ÈgÀrhnj,l÷ǶõÂßëŠm—ý©½÷ò‚Ò5úŽf€†G>£Ñh¹ƒ–=hùCµ3HhùD8ƒDpäÈ!ÛôZ›-¸Åî½æ‹É¦BwM<Å–ÌaoµÏkšÙ Õ�Ï€õú&þ¼è Ù¾±Ï¡¿­¾ÆÁÕ�Ø�•÷õ)ö/¾Íö=1­O±÷רž{Æö9vÍü»>ÇŽ«¿fÛ'ýUáØvñ–5Bì¸ú¬Òo°ïh6hxä3�ب79ƒ„ŸAbï®MöʲV{lÖÙÖÚò±dS!„f/Ì»åë¶fétÛ³ãÍÒü Ÿ�æ§"®ß„R„ö>RÅeÑèmǶ˿Töû&†@Œ=µ”©}G³@Ã#Ÿ1XÔ0Щ'‹œAB§² g�Ðì…�ëæÛÓŽÎf&¤š qÜsÕ©ÙLÝF·õŒ|ú'U Þº!YP§ å}ONïVï™3®{QûÓè^¬NùÛγÂ7¡[/ü|bAÍÛò×¥Q§ïzl6èœþ:Ýžbøðᥭ–ý?lokk+m­ÿö¢ø0øA>c yÿp×$Æ=ü­dcA¡¥ÓŽÉ–Rì9¸3»­Ž¡ Ùš• Ù ©¦BÍnÐ,ÍvЬ‡¡„|FC;r¸[!¯HòŸ}°[!¿ÁÌn…üÞ~Ô­�ß}ûyÝ ùëÎîþmòø/'‹‚h´Ø>ñÌn¿ECËqòùP8nýÇnùU8æŒë–¯½‰+îé6 ÍJ�+EâÐ[/&Ç©B±ó—¥Áî(=–m—�VöûÔÏ_˜WºFßUm6¨ÈW±Ÿ×ÚÚj“&MÊþ¯ë¨1 õÞÞ|˜ü ŸQO:Xã ›–öú :~‚Ž£ ã)謩¦B:>ÊGÇfg›ð>{¡òyhÓÝü‡ßCVwû0�šâ¾á-Ý>ìï}hb·b÷#»$;oüV÷"§£8Š?\5ˆ–îÛ(B{�T„bèÐß�ŽÑ°õâ?Èf4 U› *ø5³!OÅ¿š�~noo¯ûöÞàà àùŒZ g�¸åé+zu‰�[ÖÚ §dg„èiö¬‹OÎÎ0¡3MèŒèD>×Ï‘Ýïv+ T|ä ’ƒ/=Ñ­ÀÙ¿ôŽnÓÞG&w+Âvß9º[Q·ó¦ï$ Å|ÑIô/¶O8£Ûkœ*Æ5k"ÿ{ÓìŠüïW³0òjòäÿ^½¾ªÛßÕÁgæÔí›Pƒ§ÖïÑU› ---]Ka¶A¾ø×õÔ¨÷öÞàà àùŒZèË$4{aÚ‡má=çÚìñŸI6BLyŒÝwíiö̼Kí� •=OEK3¹þý½Û»\‡7½\V”eñò‚nÜ�ewu+ôö=v]·‚P‘/ùS¡wÅ…Ñ¿ÐÁïò¯±–5tû}¤¾-¿ÿòî¿ß'§wÿ;èˆn/¡ã-äÿ¶´œ£ÑÕë›PƒgÀ› q‘¯†ƒš4 $ò}Õ—3Hl߼ƞ›?Ñš~¦Ý4ê¸dc!Ä­ã>iOÝù][·j¶Ü·½t¯ÈSAUíÈõïÜÛ­Ø:¼e}·¢ì½öEÝ ¸÷t/ô:¢[AØ»n>§[ñ˜::û¶Kÿ¤ì±ý ½žù×X¯{þw¡ßO·ß[…éìÝþ:â½uK»ýÍèï(ÿ·¥¿7ÔïÑ€ÚlÈ/£ˆ›qñšõÞÞz¡úÀP—Ê ‚hôXôÜãvkÛOì²ÿg²± øþÏÿ‹Mxøïí®Å7ØÓ«ž°Å¿˜fÌf·\ü›É¦BˆGüªýìÊ?°y?û�­\ú@òþ‰òX·|¾m™ðÉ"”#×× .þ{çGÿ¹,¶üø¿Ø/¯ýF·xûæïw‹·î¼ÔÞ¼ëò²xcîµöúC7”ÅúùwÚk î-‹W—=fëV.,‹öçW&ÿ‚ ˆæˆZªÚlˆÔ¨b?,RÛÂv5Ô �zoï�Z¿PùŒjt‰µïýü7ò:6Bþy½fùoäSzÓë 4Þ£?´Ù *ôÃ1Tøa›"žuPïíE1ð~�ÏÈÓÁWo\Tü ë°5+nÉ–CÂ$Z—^Võ >xvÖ„Xñì­¶üÑ‹³ƒ6ö4{AÔA u0HÝÞ¼6;>‚†§ç=@¡f7ìœöMÛvÉ–oçÈõ€ ¼G~Ðl(ˆ�¯2-� ³Fôÿ˜–¯Ä³JâÙ,:~G|Yþ´¨Õn›:ȧ®Óèô˜õØ1¸Èç¡kû¾-]g�8ïž¿L6Ù$–O¶…OþÈæßñíì´“©¦B�¶R€Ôi,u:K”Ó·üšÚ¯e:h_Ñ&ª¡ 3Nì}h¢|öÁì(ûG®|â=ðƒfCA |iZ’ùáØ¢ÿÇ…µ m]W·QS"ìŒøçj·•Fn6è±ç'͆Æ@>-:ƒÄc/ßQè ÷?5Îæßÿ�vï5_L6âøÙ„ÏÙ’¹#ì­öyÌ^ˆèÈüZZ°á-Ùùù·O<3ÙHH…Î( cè8:Æ€–Vô„||!§?h6äqàÛ³ãM{eY«­Y:=û_ä› ±¸9¨3!ò—ŪÝVŠ6tmW„ë«©¶k›~ŽggÄûÕÿÃvE^~ÿñÏ¡yïC—Ól||�ñO§œ|èÅ›m¼ÿ�l,(4³ášGÿ·ÝÿÀ¹öÐÍgYkËÇ’M…š½0ï–¯Û‹‹¯·][×—îiˆ;rØozÙ,»+;¥`¶"ÑDHŶñ_¶]·þ£í{rzv Ê÷÷ï*í´wÈgÀrðƒfCAÞ¾µ+oµ£?b3.8>‹™ñÊò›K—öN(ÔC#@Ô(ÈÏ\}ãÉ%—$/ ªÝVźi6TÚ�¶Å�5þY÷7tYþ~‚Jû×cŒg6èq…Æ Í†ÆÀâ3H\üÐ7’Íň{¿bÓïÿ¶Í™=Ìîžü{ɦBwM<Åž~p´m\7ߎ9Tº·¡+[ñ¼ì`Š;oúNï–Ct\?[Ñq{@±VÈgÀrðƒfCA�>ð­[5»Ç5ŵŠ[.:ÉV/˜\ºç£TD‡‚¿7ÍÝ&„ÔªÙ ÚO¾¸×¶PüKþçj�C�!8×+ô�=E…t(´SÅ(ÄuY\àK|ýj·•¢ÍÑõtY¸~š )ùýÓlh||�i^EÎ ñý»ÿÜÆßúgvóŒ?³[Ç&9†ÅqÏU§Úò‡[lÓkmCwö‘ÃÙ1ÔÐi Õ,H5R±}ÂY3bÿ‚™YsBÇlHä3à 9 øA³¡ Fø4Ó@çrO}�®uè”o:(š¨xÎ/IE·ŠjÞA(ÊE·Éòq³ ÚmE·�‹öüÏyº<<Î|sA?‡ËÔ$ˆ÷7ª‰÷7Dû÷§}W{œ|�i.EÎ 1ò¶/Ù•3þÈn¾êwí¦ >’»BhvÖ·ÓÚW̲ý{¶”îehÑY®~$[Ö°óÆou;�d¥Ð²‰]3ÿ.[F¡³KhYÅ`#Ÿ_ÈiÀš yøt`³£?Üí¸ŽÛЗS¶©xVA­w_Ý¢ëÆ—å‹ðj·õá²|ãBBƒ"D�j6ÄûŠ/ ³0òû�JûÛÃcŠ÷‘of`pðA¦ñé �®¹½â$4{aÌM_°)×ü{›yéotÏâPƒô¾kO³�ŽµÍ¯/)ÝÃБ-‡xy�í{bZv@ÆÂË!ÆžšðQË!¬¸§s9D"Ÿ_ÈiÀš yø^~z†Í¼à›Q 5^Xt]éÒ¡%ß|€|�iL¶½’�AâŠ_|7Ù`8ÿ¶?µK¯ûw6íÇÿÊn<ÿ×’�…:†Íü;¾�Ïæà¾í¥{ðO§ŠÌ–C,¾-;…äŽÉ_M7¡STî¾cdvÊÊC¯¯ðå}E>¾�Ó€4 ò8ði†Ã §dÑ— ^Ðlzø Ót‰W6¯¬x‰óî<Ã.œþíÇWþ–ÝxQõÓRjöÂÜ©§ÛªÇÇÛ;–—îÁ¿Ã[ÖÛÁg´½üÈvNûF6!ÕHÈǶ˿d»n>Çö=v]çrˆÝï–öØ|ÈgÀrðƒfCA |€äóà g�¸}Å•É3HŒšõ'vé5Ÿµ«.ý§vÃÈc’�…·Žû¤=uçwíÕçî²÷ì.݃_ïïÝž5Ô ØõÓÈ©FB·(-‡PCâÀÊûìðf_Íeòð…œü ÙPàù<°ö½·;;ƒÄŒ%—t;ƒD6{áÆÏÛ„Ò¦þ°ú�uܦ�aÏÍŸhÛ7¯)íݧl9Äë«:—CÜ1Ò¶Oú«t#!Z:±ûÎÑÙmmX�íË3òð…œü ÙPàù\:ƒÄ¢×HžAbô-d—^óolò%Ÿ°F|0ÙX1{üg¬íîsì�纞½ Ù:ãžû/Ïf!ôj9Ä­ÿ˜ÍvÐA 5ûa¨!Ÿ_ÈiÀš 1ð~�ÏõQé ?øÙŸÙE7ü›0á7mêèc“M…š½ðÐô3]KF§‹Ôrˆ½¿¸&;n‚N'™j$äC§§ÔqtºJ§A§¯ù xCN~Ðl(ˆ�ðƒ|®�Jg�¸àæ?´Ë¯ú´]uñÇì†H6BülÂçlñœïÙ†5ÛáCûK{öAgtxïµåÙvß~^vƇT#!;®þZvF‰ýKïÈÎ0á}9D_‘Ï€/ä4à͆‚ø?È羫t‰Üqº��öïíÇ?ú ›:úCɦBˆ™?<Ážñ{qñõÙYqÜ8rØozÙ,»ËöÌ×¹"ÑDHŶñ_î\ñät{¯}‘½¿Wi§è ù øBN~Ðl(ˆ�ðƒ|î�ä$îþÏöÃÖ?°Ë'Ú®{b²©Ç]O±§m×Íw3{![ñÂ<ÛûÈdÛyÓwz·¢ãúÙrˆŽÛÙ¾±´Gôù øBN~Ðl(ˆ�ðƒ|îÙžƒ;»Î 1bÎ_g †‘³ÿ“]|ý)6qü?·©£ªŸ–R³æÝòu[³tºíÙñfi¯ÍK3 Þ[·4›y åš‰�j$¤bÇ”¿Íf:h9„f>hj‡||!§?h6ÄÀøA>§é  ÖÝwô Ùì…ß·+~ò¯ìš OH6â¸çªSmùÃ-¶éµ6;räPi¯MèÈáì jì¹glÖ,H5R±}ÂY3bÿ‚™YsBÇl@}‘Ï€/ä4à͆‚ø?Èç£Â$&Ïÿ¿�³nû’]|Ý¿µ+/ÿu»þü_M6BÜrÑIöج³í•e­¶wצÒ›�Îêppõ#Ù²†�7~+[æ�j$äCË&vÍü»l…Î.¡exä3à 9 øA³¡ >À�¡žÏ¯o]“�Aâ²G¿cß¿ûÏmÌÌS튟|ʦ´Ÿl*Äqï5_´�ŽµÍ¯/)í­¹dË!^^`ûž˜–�±ðrˆ±§f|Ôrˆ+îé\�†Àû3à 9 øA³¡ >À�¡–Ïá ?öºì çßö§6îºß±Ÿ\vR�³f]|²Í¿ãÛ¶nÕlÛ¿gKi�ÍA§ŠÌ–C,¾-;…äŽÉ_M7¡STî¾cdvÊÊC¯¯b9Dãýð…œü ÙPàÇPÈçp‰[—O°–9_³17}ÁÆOü—6eÌG’M…ÓGc÷]{š=3ïR{gÃòÒÞšÃá-ëíà³ÚÞ~d;§}#›��j$äcÛå_²]7Ÿcû»®s9ÄîwK{D3àýð…œü ÙPà‡×|ŽÏ ñÃÛ¿l—^óYûɸbÓF|0ÙXqë¸OÚSw~×^}î.;¸o{io�íý½Û³Æ€»~úYà ÕHè¥åjHXyŸÞ¼¶´G4+ÞŸ_ÈiÀš 1ð~xÊç®3HÌ?Ï.šþmÂÄa׎ùp²©â¦QÇÙÜ©§ÛªÇÇÛÖMÏ—öÔ¸²å¯¯ê\qÇHÛ>é¯Ò�„DhéÄî;Gg·=´au¶/øÂû3à 9 øA³¡ >À�fÏçp‰«îÿ¦]zÍ¿±É—|Ânñ�dc!Äí—ÊÚî>ÇÞxq®½w`wiO�I³ tÆ=÷_žÍBèÕrˆ[ÿ1›í ƒ@jöüãýð…œü ÙPàG3æó«ï¾`®šjo9Ý&LøM»ö‚ã’M…ÓGkM?Óž›?Ѷo^SÚKãÑé"µbï/®ÉŽ› ÓI¦ ùÐé)u\�®RÇiÐé+14ñþ øBN~Ðl(ˆ�ð£ò9œAâöy#ìÊë~×&_òñg/ÜvÅoÛÂ{ε k¶Ã‡ö—öÔ8tF‡÷^[ž�áa÷íçeg|H5R±ãê¯eg”Ø¿ôŽì ,‡@Àû3à 9 øA³¡ >À�FÍç‡öÙÊuÙÍw�m?ùñ¿´©£?”l*„¸é‚ÛC7ý¥½¸øzÛ¹¥Átxä°Þô²Xv—í™3®s9D¢‰�Šmã¿Ü¹âÉéö^û"{ÿ®ÒN�îx|!§?h6ÄÀøÑHù¼kÿV›¿ôj›>ãt»J³M…8f]ñ)[<÷öVû¼†š½�-‡xaží}d²í¼é;½[Ñqýl9DÇí�lßXÚ#P ïÏ€/ä4à͆‚ø?;Ÿ7½ó¢ÝÿÀ¹6í'¿cSGýZ²©búèãì¾éak–N·=;Þ,íapi¦Á{ë–f3´B3R�„Tì˜ò·ÙL-‡ÐÌÍ€úƒ÷gÀrðƒfCA |€�ÏG޲WÿÌîøéWíúK=ÙTˆ£õòß²'ï=×6®›ŸÝvP9œ#AÍ�=÷ŒÍš©&B*¶O8#kFì_03kNè˜ @­ñþ øBN~Ðl(ˆ�ðc òY³kö†5Ûü»þ—µ^úɦBWŒø€M¹ôŸÚ­³¾jËWÝbGÞØ!Þ²Þ>û í}àG¶sÚ7²Ù©FB>¶]þ%Ûuó9¶ï±ë:—Cì~·´G 1ñþ øBN~Ðl(ˆ�ð£7ù¼}ó[½`²Ý7íÿ±ϯ~ì…©£�µ«òoìî¹o¯½½¼´‡ú{ïö¬1 Á®ŸþCÖ0H5ºEi9„VÞg‡7¯-íh¼?¾�Ó€4 bàü¨–ÏïØmo¼8×Úî>Çf]úÉdS¡+F|À&_ò »î¦Ólîâ ¶ewý—GdË!^_Õ¹⎑¶}Ò_¥ ‰ÐÒ‰ÝwŽÎn{hÃêl_@³ãýð…œü ÙPÐüt*É{®:ÕfŽù¸Ý=éóÙϲuÓó¶êññöÀ´3lúùǦ ¥¸v̇mÂÄi×ÿì,k{ånÛ¾oK¶�zÑl„qÏý—g³zµâÖÌf;è �šýxÄû3à 9 øA³¡ > ¹©±pÓè—5¦ŸÿkvË…'•mËÇ´´ŸŒû'6þÚSì¦_ü[þÆã¶ï½Ý¥½Ö–N©å{qMvÜ�N2ÕHȇNO©ã2èt•:NƒN_ ¼?¾�Ó€4 bàšÛì‰ÿ6ÙL¸aD÷mSÆ|ÄÆOü—vÙͧÙmK.·Õ١õ]r 3:¼÷Úòì »o?/;ãCª‘�ŠW-;£Äþ¥wdg˜`9†2ÞŸ_ÈiÀš 1ðÍkï®MvÃÈ_íÖTPLShöÂe'Ù¸ë~Ç.»û«vïsÓì•Í+kw‰#‡íð¦—íÀ²»lÏœq�Ë!M„TlÿåÎåON·÷ÚÙûûw•v @x|!§?h6ÄÀ45ÏùžMUù8 ×]p¬�ä;öЋ7Û†mµÉól9Ä ólï#“mçMßéÝrˆŽëgË!:nd{ý8 4;ÞŸ_ÈiÀš 1ðÍ£R“áÆÜ’‰©çcnþRéV}£™ï­[šÍ<ÐrÍDH5R±cÊßf3´B34@ïðþ øBN~Ðl(ˆ�h|÷m·�޵_ÖTPè•—]ó¯mJËñ6mä³ã2Œ›ñöì[ J·.àÈáì jì¹glÖ,H5R±}ÂY3bÿ‚™YsBÇlм?¾�Ó€4 bàWh2Ìsb·&ÃÕ}Ô~Øúû6cÉ%öÔº{íŠ_|×~pïWì²G¾Óc£Agu8¸ú‘lYÃο•-sH5ò¡e»fþ]¶ŒBg—в õÁû3à 9 øA³¡ > ñ>´¿j“áÂé¿—5ÞÚ±.»¾Îþ SDn¹ìK¶cêÏ~²å//°}OLËÈXx9ÄØS³>j9Ä�÷t.‡0`x|!§?h6ÄÀ45V/˜l·ŒýõBMQcaÛ¸?.kl»ä‹Ù „“¿Z¶½Zè•»ï™�²òÐë«X 2ÞŸ_ÈiÀš 1ðƒ¯Z“AÇ`¸øúSº5‚�×#Ù<ØzáçÛ:cÛå_²]7Ÿcû»®s9ÄîwK{Ð(x|!§?h6ÄÀ ž#GÙ+ËZí§—üóÊM†Åc“Mѱ¶öt¼…Òrˆ½üȬ¼Ïo^[º5€FÆû3à 9 ø1 Í†ÖÖV6lXWèç`øðá]ÛÛÚÚJ[뿽(>`à…&í—ª[“áÚ Ž+ÔdÐY#ÔHH6:bÛ_¶CVÛû‡–n ™ðþ øBN~ x³!n0Ú6iÒ¤ìÿj¨1 õÞÞ |ÀÀªÔd˜:úXwíçlÆ¢ ûÕdPl»ì4;øÂ¼Ò­4#ÞŸ_ÈiÀ�†h6¨øW ÐÏíííußÞ |ÀÀXÿü½vÇÄWó&ÃîÛϳKfÛŽ«Ï²­ÿ�mÿÉ_Óhàýð…œüðfCXÊ/gÈÿ---ÙeõÞÞ |@}Ul2Œ:Æ.¿êÓýj2äOGI>~�Ï€/ä4àÇ€6bsæÌÉšRï¦B¥í½¡Š ˆÚÇÓOβŸ^þ¯+6®yèmÑs�'o»vÅSööÍß·w/úB·&æÿ—½ºôÑäí‚ ‚ ‚¨ÔRáfƒhvƒ¨ÿ¡9Pïí½Që ê6®›o÷Lù£nM†ëÏÿÕΙ .¨ÙL†<òðƒ||!§?­Ù ™ ša :xcêŽõÞÞ |@mTj2LñAåoÙMóGÖ­É�Ï€ä3à 9 ø1 Í5âc6Äâíñ¬ƒzo/Š�èŸw7®²ûn8cP› ù øA>¾�Ó€Úlhf |@ߨÉ0wú™Ýš 7Œø€Møñ'´É�Ï€ä3à 9 øA³¡ > w¶o^S¹É0á7mƼÿ;àM†€|ü Ÿ_ÈiÀš 1ðÅìܲÖžõÿÚ�#>ØpM†€|ü Ÿ_ÈiÀš 1ðÕUl2tÄ•—ÿºÝôè? z“! Ÿ?ÈgÀrðƒfCA |@ÚÞ]›lÞìÿ¯r“á‘¿o˜&C@>~�Ï€/ä4à͆‚ø€rj2<~×ßÙ #�éÖdøÉe'5d“! Ÿ?ÈgÀrðƒfCA |@§ý{¶Tl2L¾ävÓýßiØ&C@>~�Ï€/ä4à͆‚ø0ÔÜ·ÝÌ=Ïn¸àÃMÛdÈgÀòð…œü ÙP†ªjM†«/ú¨Ýtßÿhš&C@>~�Ï€/ä4à͆‚ø0Ô>´ß>|Aå&Ãχ5]“! Ÿ?ÈgÀrðƒfCA |*ÔdX2ïb›>æ£Ýš ×\xBS7òðƒ||!§?h6ÄÀïª5¦ŒùˆÝ4û,Û°-�ÍÒdÈgÀòð…œü ÙP¼:rä�=Ó6Ù¦�ý„û&C@>~�Ï€/ä4à͆‚øàMh2ÜxñIÝš SGkÓoÿª»&C@>~�Ï€/ä4à͆‚øàÉ3 ¯©Üd˜u¦mx÷ÅÒ5Ë5{“! Ÿ?ÈgÀrðƒfCA |ðà¹å3lú¥¿1d› ù øA>¾�Ó€4 bàC3«ØduŒMŸqº½þÎêÒ5Ëyk2ä3àù øBN~Ðl(ˆ�Íè¥çï¶›®ø•› ¿\Yºf9¯M†€|ü Ÿ_ÈiÀš 1𡙨É0c§»5¦�øà�n2ä3àù øBN~Ðl(ˆ�Í j“áú?²WßZRºf¹¡ÒdÈgÀòð…œü ÙPÙºµ¿°“O¡ÉPù øA>¾�Ó€4 bàC#ªÔd¸aÄìÆ)¿G“¡òðƒ||!§?h6ÄÀ‡Fòú‹læUÿ¡b“¡ýµÇK×,7Ô› ù øA>¾�Ó€4 bàC#P“aÆu¿Ÿ5h2ôù øA>¾�Ó€4 bàÃ`ªØdèˆ'}Î^zenéšåh2¤‘Ï€ä3à 9 øA³¡ > †77­²Öÿ”&C�‘Ï€ä3à 9 øA³¡ > ¤ÐdÐÙ$º5~ô)š ýD>~�Ï€/ä4à͆‚ø0ÞÞ²¦j“á¹çn+]³M†Þ!Ÿ?ÈgÀrðƒfCA |¨'5n¾ù?ÛÔQ¿F“a�Ï€ä3à 9 øA³¡ >ÔCÕ&ø“må²K×,G“¡ÈgÀòð…œü ÙPjióö×ìæY_©ØdXñôõ¥k–£ÉPä3àù øBN~Ðl(ˆ�µ�5nû›:úØîM†‹O¢É0@ÈgÀòð…œü ÙPú£Z“ᆱŸ°¥OM°#G•®}M†ú Ÿ?ÈgÀrðƒfCA |è‹Í;ß°ŸÞùßmJËGh24òðƒ||!§?h6ÄÀ‡Þ¨Öd¸qÌ ¶èñq4ù øA>¾�Ó€4 bàC[vo´ŸÞóÍŠM†¶_Œ±Ã‡ö—®}M†�E>~�Ï€/ä4à͆‚øPMÖd˜óm»ú¢i24òðƒ||!§?h6ÄÀ‡”jM†FgóïÿG;¸o{éÚGÑd\ä3àù øBN~Ðl(ˆ�±¬ÉðÐßÛU—|œ&C"Ÿ?ÈgÀrðƒfCA |�®&øÒ½É0ò{bÎ?Ðdhä3àù øBN~Ðl(ˆ�ohS“áÖGþOÅ&Ã/~öÛ»kSéÚGÑdhLä3àÀöùfÏœj‡|ÜlÅç;Ðôx�ü ÙPßД5;Ï&]ñÏh28B>MN�…¶ãÍžìøØbá‰fïÎ-]@³â=ðƒfCA |CKÕ&ÈÚƒ?ýo¶këúÒµ�¢ÉÐÈg`�xÓlÇØ©Ø¹¤³i Ø<ÛlSkg¼1ÞlýØÎX{®ÙËßîŒç¿böìé�Ñö‘òFCˆ¿f¶ôSå±ò‹Go—�çÎ8ºÿT¬ûÞÑÇ’Š·§}Üùøå¬£Ï/»W}-Rñþ¡Ò‹ -¼G~Ðl(ˆ�ohP“á¶ù£mâøß¨ØdعeméÚGÑdh.ä3�pdÿÑBwEñ¶yG h×q±æqûO“n }�Ç•¿Æùв’ø÷‘�5ß,ÿÝÅÑ~Nùï8oM)oªäC/qS%Ž�KÊ*ù8¼»ôLj¡Œ÷hÀš 1ðùš ?þñ'í†èÖhxà–¿¡ÉàùŒ¦ôÞ–£EÙ®åG ¸-÷-ôÞœ|´(Ô·ò¡€|ñëG ÍgN=Z”.>9]ÌD#FÜPÉDzϕ7Tò¡ˆ›*ùˆ*ùØ0±¼¡’-߉›*ùˆ*ùP^£“^+ŽÃ¸B³¡ ŠŸzj2ÜwãŸÛ»W•®}M†æF>£.â".4â¢äõK�0¯|÷h¡³úÌ£E‘ЦP@éìTÑÕlÑvBº(Ôó¯�^�ðÚèu ¯Y~9‚^ß·g˜-̳¡íD³wî*oʤ"ÞW>T4Æ¿¯|ÄK–|²ü¹C'ÔLýM„Hý-…ÐÒ£Ôßbˆ×F§ÿŽq¦B¹–Ê¡š•Ê?…–Såé6‡p‡fCA'¾dÇdX8Ö~t忲i#>H“aˆ!Ÿ‡ CÛ�~ÐÝóüÑÄqq¹ñú£´õ!<| éì£ÞW�vôC¾§0.h4ó!<ßøÛàWG}}4ƒ"¼nšY^O͸¯s½¿±ÕïnÙgíý§>döô§;‹Ÿ¡JËÂ랊�Ññ1ò/“IE<[&ZvþFò¡åáo)úö:üÝ¥‚%8Dê8,‡SOЏ�™ŠxKE*BÔºÁ 4 ¢8ñ¡§&ÃÝ×ü¡½³¡ãÃrM_ÈçT«ÆÅËÂ�¥?¼6[¨à ÏééÏ”°Ž?DǘãÆkè÷­=ú:ë �ÏC” þ–S¡p†¿ûThÆLÈ‘|Ä�ÇTÄK”R¡�Æy‡Xò9š”ß¡÷«ÔßC,hR4 âÃLsS“áöÅ—ÚøŸüvÅ&ÃÆuƒfMŸÈç^âÀ�Õ#þ�˜/žÂ¬æHxmâõßj¦„×sgtð<>¤F>cH‰³ùˆÇçT¨p cO*â1<ñl¯TÄMßTÄc>�”[ ßAƒÅ7½Vu8KÇ_ŽO|˜iN¡ÉpÅÕÿÚ¦Ž:†&2îò™V-wH}x‰�ȯ]Ö·šáu‹ü¦åáuæ¨ù ƒ÷gÀ�µ ?Z>v‡ã°Üttì͇fk…1:ñû`*âãèäƒ Që* ݦNÇ`験O|˜i.=5fÿøwlýóo@94œÓà7GºŽå0p+â�žÖæÀ�!0dðþ 8ÁqXŽ¢ÁBxŒŽü½ô¬µµÕ† fmmm¥-fÇ϶ ôö¢ø0Ó²&òÙ¥×ýM”ÓpªËºùŽ£oà8°{4ã�1¤ðþ øBNQ4Xü‡–ÏöSÇ^z¦Â¿¥¥¥«èWóaÒ¤IÙÿµM—K½·÷_c+k2Œ>¶[“aÖå¿E“a¨[ö;é�¯™ƒ¼?Î�Ó2†ZƒE3–ú©c/Õ©èWÁ7Tü‡ÿ‹~noo¯ûöÞ`àkLj2ܶüÇvéuÿ®b“á•e­väÈ¡Ò-:Ñdôí¹¾§As°Ž?À��ºãýð…œš\µc°ôSÇž*SÁ¯&ƒä› qñ.«÷öÞ`àk,¡ÉpÉõÿÁ¦ŒùMtRQ®¢]§‹¸jfpà@ )ñþ øBNÔé,ŸÜ+‹‹þ¸à¯wS¡ÒöÞÐÀG ~,[½Ð¦=ÞbWh2Ìlù„=qÏ…öòË/•Ýn튧ì훿oï^ô…nM†M7þ/{ué£e×'š#^]³È6­o;ŸþjçASÍ„R¼ÿä±Þ¯–m;òÔ öös×$÷MAADÿ¢–:>Á§©ØhŒcΜ9Y3 .þCs ÞÛ{£Ö/z';&Ê+í¢é_H6nûOmõ‚ÉvøPùZsf28óþ!³¹¼¾¥ó`…Qã ËO霙 Ùº-GºÜáýð…œü°fC^<»@ÇqHÀ±ÞÛ{ƒ�opÄM†«/úh·&ÃŒ1'ÒdðNÇ(ÐA uö!ÕT¡Ëu†]¿Ê± ÈgÀòð…œühˆfƒÄ³âYõÞ^ßÀ*ÒdXñèX;¸o{é�h28 š‰  š™�j*Ä¡šé ºmä3àù øBN~ Z³¡Ù0ð ŒÐdÓú‡6ù’OÐd*t`E|Qgih;!ÝT¡Sê莿œÕyÊ > Ÿ?ÈgÀrðƒfCA |õÕS“aú¨cmÉÜ4¼8²ßlëÃf¯Ž0[ö¹tS!�]BgƒÐÙ&j€|ü Ÿ_ÈiÀš 1ðÕGh2üð§R±É°xÎ÷lï®òo°i24¡}kÍÞšb¶úL³Ç¥› !ŸÜyÚÉͳÍ•7˜j�|ü Ÿ_ÈiÀš 1ðÕVW“aÖivåå¿N“Á£Ã»;ÏþÐ~ŽÙÓŸI7B¾�Ó€4 bà«�› #ÕæßñmÛ¹emé�h24‰½kÌ6L4{g/,ùdGb}·óô“jL òðƒ||!§?h6ÄÀ×?¡É0ú¶ÓíÇ?ú š ^¨I f�šK?•n*„PóAMˆ7Æ›íy¾´ƒÁA>~�Ï€/ä4à͆‚øú&n2L˜ð›vÈtk4<6ëlš Íd÷ªÎ†�–=hùCª±B -£ÐrŠž½P ù øA>¾�Ó€4 bàë�Ðduû—+6m=ËÞÝØQ¸Fh24  QjÔuàÆTS!„f/è�:¤–T4(òðƒ||!§?h6ÄÀWLh2ŒüÙŸÙø+˦�ø M†f¤SLê`�«Nëyö‚N]¹öÜÎSYê”–M€|ü Ÿ_ÈiÀš 1ðUW¤É0wêé¶ùõŽ"6B“¡A¼·Åì—³ÌÖ|ÓlÑIé¦Bˆ¶ÌžÿŠÙÆëÍö¯/í ¹�Ï€ä3à 9 øA³¡ ¾´®&Ã�n—OþtÅ&ÃÆuóK·èD“a�½ÈlG›Ùú³•_L7âX~ŠÙ«#̶ÍkšÙ Õ�Ï€ä3à 9 øA³¡ ¾reM†«>mSGÓ­Épßµ§Ñdh$7™mj5{él³…K7Bhö‹_7{{ºÙ�7K;ðƒ|ü Ÿ_ÈiÀš 1ðu*Òd¸{Òçmýó÷–nщ&à Ðì…íóÍ^Ý93!ÕTˆã™S;g:è6º­cä3àù øBN~Ðl(h¨|¡ÉðƒŸŸiã®ýœM},M†F¤c(h6‚f%hvBª©B³4ËA³4ëaრàù øBN~Ðl(h¨|Eš ³ÇÆÖ­š]ºE'š DÇOÐqt<�"ÕTˆCÇgX?¶ólÎg/TÃÀòð…œü ÙPÐPøB“἟ÿ»øº[±ÉðʲV;rähÑJ“aì[köÖ”Î3Bô4{añÉ�g˜Ð™&tÆ dø øA>¾�Ó€4 *_Y“áúSlʘ�Ðdlš½°õa³µçš=ý™tS!ÄSǘ­:ÍìõKÍv-/íy|�ü Ÿ_ÈiÀš yøŠ4f]|²­^0Ù:zêCš u²w�Ù†‰f«Ï4[p\º±bÉ';þ@¿k¶y¶Ù¡í¥ >È~�Ï€/ä4à͆‚¼|]M†{þÒ.œþ{6¥åxš ƒáðn³wçšµŸc¶ôSé¦BÍ^xöt³7Æ›íy¾´ôd?ÈgÀrðƒfCAÞ¾|“áê‹>Ú­ÉÐÚò1{nþDš õ¢F�j¨��j,„PB�ˆ-÷v6&Ð/|�ü Ÿ_ÈiÀš yøÊ› ÿ±b“aÅ£cíྣSòi2Ô€–8h©ƒ–¾�Ó€4 jö�/n2ü°õ÷mò%Ÿ É0t�F¬Qmìiö‚þ¨ƒ@ê`�:($ê†2€ä3à 9 øA³¡ føŠ4nuœ-žó=š µ ÓKê4“/»ó´“©¦B�¶R€Ôi,u:K >È~�Ï€/ä4à͆‚šmà‹› £úÇU› {wm*ÝŠ&CŸì\b¶~¬ÙÊ/¦› q,ûœÙ«#̶Ícö ⃠àù øBN~Ðl(¨Y¾|“áÊ˽[“aúÈch2ôÇÁŽ×mS«ÙKg›-üXº©B³^üºÙÆëÍö¯/íƒ�2€ä3à 9 øA³¡ FøŠ6æßñmÛ¹åè”}š ¼ÈlG›Ùú³gNM7âX~ŠÙk£Í¶Ïï¼-d?ÈgÀrðƒfCA�:ðÅM†óoûS›0á7토ÉÐ_Þ4{{zç¬ÍNH5Bhvƒf9h¶ƒf= áñAðƒ||!§?h6Ôh_‘&ƒbÞ-_§ÉP„ŽŸ ã(hF‚f&¤š qh†ƒf:hƳšd?ÈgÀrðƒfCA�2ðÅM†ÜqzÅ&ã­gÙ»W•nE“!IÇPбžÿJϳ�d¶æ›�gšÐ'ÐÔø øA>¾�Ó€4 ì�/ßdåoÙ´¤ÉК½°õa³µçvž"ÕTñÔ1f«Në<Ë„Î6Wø øA>¾�Ó€4 ¬�¯h“aîÔÓmãºù¥[Ñdè²o­Ù[SÌVŸi¶à¸tc!Äâ“Í^þ¶ÙæÙf‡¶—v�ø øA>¾�Ó€4 è�¯¬Éð³?³Ë¯ú4M†"ï6{w®Yû9fK?•n*„Ðì…gO7{c¼Ù®å¥`(àƒ àù øBN~Ðl(h ¾T“aê¨cº5î¹êTš ÁžçÍ6L4{g/,ùdÇ/ó»fïÜÕÙ˜À�ÄÀòð…œü ÙPP½¾¸ÉpÞ�gTl2Ü=éó¶þù{K·¢M5 Ô,PÓ@̓TS!„šjB¨±wMiêø øA>¾�Ó€4 ª×À7¾÷ŸÛ¸k?gSGK“!OË´ÜAË´ü!ÕXñôg:—Qh9³�ÀÀòð…œü ÙPP­_¨¢M†Ùã?cí+f•n5„š :@£Ô¨6êÀ�©¦BÍ^Ð u Hèd?ÈgÀrðƒfCAµz¡â&Ã÷îþÏvñÔW±ÉðʲV;räPv»!ÑdÐ)&uªÉ•_ìyö‚N]¹î{�§²Ô)-�^àƒ àù øBN~Ðl(¨¿/T·&Ãõ§Ø”1éÖd¸ýòO �&ÃÁMf¿œe¶æ›f‹NJ7B´�`öüWÌ6^o¶}i@ßðAðƒ||!§?h6Ôתh“aÖÅ'Ûê“íð¡Îoé]6Þ?d¶£Íl}‹Ù3§¦› q,?ÅìµÑfÛç3{5ÅÀòð…œü ÙPPo_¨²&ÃÏÿÂ.œþ{vÍ…' ½&Ã�7Í6µš½t¶ÙÂ�¥› !4{áů›½=½óv@�ðAðƒ||!§?h6Tô…J5®¾è£Ýš ­-³U��÷×dÐìÍDÐŒÍLH5âÐ ÍtÐŒÝ|�ü Ÿ_ÈiÀš õôBå› cfžZ±É°âѱvpßöìv.š :†‚Ž¥ðÂY�³RM…:6ƒf9h¶ƒŽÙ >È~�Ï€/ä4à͆‚*½Pù&Ã[ß&_ò ßM?aÛ<³WGtž"ÕTˆCg—ÐY&t¶  ðAðƒ||!§?h6”¡Š6nuœ-žó=Û¿gKv»¦m2ì[köÖ³Õgö<{añÉf/Ûlól³÷:Ÿ7ÐHø øA>¾�Ó€4z°ö�çlÒçÚè9ÿÕ&>ö÷¶â�'Êš £úÇö“ËNªØdØ»«s©@Ó5ï6ÛúpÇ p®ÙÓŸI7BÙñ×ó]³wî2;Ô¹4h|�ü Ÿ_ÈiÀš UhFƒ–G\=öD›:úCÙÇ^Š]yù?K6æßñmÛ¹emvÛ¦h2hö‚šíç˜-ýTº©B͇gO7{c¼ÙžçK;šd?ÈgÀrðƒfCãf�nÓFþj·ÆBM×dP£@ 5´ü!ÕX¡„ïÎílLNðAðƒ||!§?h6Tqý¸šl0Ü0¢óßG[ϲí›×d×mØ&ƒ–8è@�:`£–>¤š !4{A€ÔR -©œâƒ àù øBN~Ðl¨bfËǺ7²ø€½»qUv�†l2è �:X£ÚØÓìüQ�ÔÁ uJK`àƒ àù øBN~ x³aذa]ÑÞÞ^Új6|øð®ímmm¥­õß^ÍÝ“~7Ñhø›}ŧ«É ÓKþr–Ùšoš-:)ÝT¡ÓV>ÿ³�×wžÎ‚ø øA>¾�Ó€Úlhmmí*ôõÿI“&uû¿.Wc@ê½½'o¼8×Z/øHY£aæè�ØKÓÿvp› ï2Û¹ÄlýX³•_L7âXö9³WG˜m›Çì d?ÈgÀrðc@› 1ÿjˆŠ5ý¬YõÞÞ“÷^[n/^ø;vûÈcí¦|Ànq¬=÷Ãßœ&ÃÁMf›:^¯—Î6[ø±tS!„f/¼øu³·§›x³´d?ÈgÀrðcÀ› a)CKKKiK÷â_—©9Pïí=Ùyý7º5:ãóÙ¿um2höÂöùf¯�6{æÔtS!ŽŸï¼®n£Û¨ˆ2€ä3à 9 ø1à͆@³TôK£6¶�ÿr®ÉPŠ‹þc}š š… Ùš• Ù ©¦BÍnÐ,ÍvЬ…ñAðƒ||!§?­Ù šá jÄÅhÔ{{Ov\÷ߓ͆w&œ™½pý�ö—WÛ›«[mÛŠÿiþvº©ÅþÅŸ·wWœkž¿ÝÚ_y)¹O‚ ‚ ‚ ‚h„¨¥Žª¸2Í(æÌ™“ý¢ã7¤àXïí=yïå¶í²ÓÊ úùà óJ×èƒýë;Ï¡3Bô4{Ag–Ð&t¦ �q@MÔzà0xÈgÀrðc@› j0„c6„Y A¼=žuPïí=QÃaÇÕgÙÖ‹ÿÀ¶ÿä¯{ßhÐÙ¶>l¶ö\³§?“n*„xê³U§™½~içÙ&Ôd?ÈgÀrðc@› ͬW/ÔÞ5foN6[}¦Ù‚ãÒ�…‹O6{ùÛf›g›Ú^Ú€z⃠àù øBN~Ðl(¨ê ux·Ù»sÍÚÏ1[ú©tS!„f/<{ºÙãÍö<_Ú€�ÄÀòð…œü ÙÐ�Jò™Síð‚�wž^R?‹j¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4Ðüæ¯Ýn§NzÆ>>z�}~âŠìçZ Ù á‘Ï€ä3à 9 475Ž?¿Í~å{Ovʼn£ÚÜÞ-]£ïh6hxä3àù øBN£ÙlÙóž­ßº¿ÇXõÖî¬ï)T”·>½©Ç˜¾ämûðúBñíÛ_.gL}ÎN¿öÙã” ËíSã–&ãC#”5B|öŠe¥W¬ïh6hxä3àù ø "«Ó®Q]ª(NEÛ«;Ê âJq׳ï$ ã|L~òÍdQœ�÷½š,ŠóñÍ[×$‹âT¤ äTsÞSÉ¢™è[èõì/š ù øA>ÍOEjo§]ï>p8Yçcí–}Ý âJ‘*ŠSq飯' ã|œûóµÉÂ8gÍx!YçC͘TQœ�O^¼¤ìµ$ˆ�ï'¶uħ/{º”½}G³@Ã#Ÿ?Èg``å‹üøï{Woé*ÈóS¼Ï¹³½«¸>û–—ÊŠèc+L»&ˆF‹“/\œlðäC³sâ¿ñJñ•éÏ—5ž*…ò'Χj7ƪżW¶•5Û*ÅšÍ{Ër¾Rlßw(#Ô$üèè…e¯Û‰£Ú²Y/ýE³@Ã#Ÿ?ÈgÀ²ùáþ›üÙ+7wS¼UV”|÷ŽWºŠ™øÛõÓ®YUV8G3ÀEÄ¿Ój¡ß\W 5�₸R|ïÞuew•bâÊŠáJ¡¿éøo¼ZÄÅpµ8täýR6¡ÔpÐ1>ôƒ§² µh4Í �|ü ŸÑ¨6í:ØUÈ<ÿöž²(.œâµë-•ÈíÌi«» »üúT1Ù´QaÚuˆFµ•=÷Jñ¹Žâ&.†«Eü:W ýN₸R\¿hcÙïµRhöGü·P)–oØUV W ý��ªÖïÑ44<òðƒ|FoÄEšŠ¹PØi:q\Žì�®"2€º¸`��È>ÖÉÇ…}iÌ¢²Ö[èèîqà‹“Wv½ùµàñë§åáu�µü—e €x}¶Né7Pê5íÀà¡ÙPf?ÈgÀò¹öòŒ Ñø´vš6°ñúÿ¯·¾XÖˆ bM‰OÎ^"?å?~ôº„×(À·x¾^çøu�úýxFN~Ðl(ˆ�ðƒ|ü*ù¬o˜C±©ožãBTßL‡"5îúPØ*ôMw(zõ x\{?Ÿ¼f8„çš_ׯóó‡×H3&â×/¼® }ó^sÍÀˆš¡�Úà=ðcÀ› Æ ëŠöööÒV³áÇwmokk+m­ÿö¢ø?Èg ù©àÓó>>zA¶v\?´7·è*6µö<¢ùõÿZ³ŠW­e�ZëŠ^=�¸�*š=…Žqžkþ4qñk¤c&„×OÇRˆ_ÛøÔmùê¡9ñ ø1 Í†ÖÖÖ®B_ÿoiiéúÿ¤I“²ÿër5¤ÞÛ{ƒ�ðƒ|š› ËãÏ/ŸŠâè…6çù-eÅf¼þÿá5[ËŠÔK}½«€ÕiÙBa«o¹ã¢7> Î¯ß§·È¯ÿW3'¼:+B܈�ЯÙñk^s…ÎÂ~5Eð ø1 Í†˜ŠþÐlPñ¯Ÿý¬YõÞÞ |€ä3Иtžs¥á4�:EœŠ×pf€Ð øèèò£Ö{ŽãF,(kÄç¿?kÆ ]Å¿Ž—ŠE|Àü9é×nÙ×ÕØ¾ïPéÕïÑ€ƒÖlÐLÍ8�|ñ¯&„šõÞÞ |€ä3P_*`UȆS ê˜Ó—¼�Áa� eÌáÌÍtÌ€�]°°«øÿÌeOwÿ �!4òÔkñ¹öÛ^ÝÑUü+Xÿ�¡Œ÷hÀ�Ai6„@Pï¦B¥í½¡Š ‚ †R<õÌKöÄŠ—lÖüçí¦ÇVÛøûVÙïZiç޺¾9c¹ý×iËì�¯\b_˜°Ø~óÂ6;é‚Éâ¼æñýĶŽPÃB�ã_�[hðãÅ]ñÕ©Og�Uñ?f.Ï¿âÿÞ¶"{N!ôõ\?_øBöÜC¼ôrú5"‚ ¢rÔR�Íý:HcLÍ€¸øÍ�zoï�Z¿PùŒ¡$¿4!œº0^š oãõ­¼ÖèëÛúÁ:5a8` K8F€Ž§ Ç–耀:ûñ£Ê—RœØñ˜9/?Ðüx�üÐfƒ |5ò…¾–T¤àXïí½ÁÀøA>£…¥ á ‡*¼ÃÒ­_…y8µaXšãá ƒ:°¢Ë×[_Ì›–Oè±êq‡c„³ì>p¸ô,‹Ói?ÛñÛá :š¿ q­ñWa®Ó!ê@€*ÚO³(YÔ×;Âq ¾8yeÖ4Ðã !Ô©õ¸Uðëy¬Ù¼7k òð…œüÐfC3càü ŸÑ_:€ŸŠëUoí.[š Ó)ꔀ*ÌÃÒ„ÏO\‘탹4!Ä0¿4áúE³Ç­¥ á …[ö¼Wz–Í�||!§?h6ÄÀøA>#Pa�_š <µ4Aû`-M§?Tã"^𠯆zÜai‚ }]šÐŒÈgÀrðƒfCA |€ä³?*®ÃÒMíWñ­©þñÒ都4A÷›_šN�˜_š ç£ç…êÈgÀrðƒfCA |€äscÊ/MÐ7õ*¾ã¥ úF_EzXš oüSE}½C÷–&h惛fBÄKôšuiB3!Ÿ_ÈiÀš 1ð~�Ïõ–&¨ÐÖ±Tx‡¥ :V€ óxi‚Ž)�*èëù¥ :Æ‚[¼4AÇbˆ—&¨!‚ÆB>¾�Ó€4 bàü Ÿ‹Qq­³ ¤–&è¬*ÌU¤kI€Šv�½ UÔ×;ÂÒ-‘ÐYô¸ÂÒ�åA�;^š ³AÀòð…œü ÙPàÇPÊg$PMƒåv•-MP!ž_špʄ僾4ásW,K.M˜¾äíìqë9è`ŽzNÛ÷*=K e¼?¾�Ó€4 bàühÆ|Þ´ë`·¥ S¼U¶4A§5T¡®¢}°–&èôŽºÿS'=S¶4A�3¿4áù·÷°4ýÆû3à 9 øA³¡ >À�ÁÊçCGÞ/[špïê-Yñ/Møæ­kbiÂÉ.Îî?^špîÏ×f�3,MxxÍÖìyè9±4ƒ…÷gÀrðƒfCA |@óSa¬oÜ?>zAvP@ýÜù¥ ³–ÿ2+¾Uˆ�žûZV˜Ÿ5ã…²¥ Çœ÷T²¨¯gè>ußai‚S~i‚»žKЬx|!§?h6ÄÀ47ÔÇŸßVVŒtÔB›ÑQpë²üÒ}‹Ÿ_š oûãÛTä—&höCXš0þ±7²Ç­Yzai‚fQCïÏ€/ä4à͆‚ø€Æ¤éû*®ã†A8Íb˜e  ~tôà,Gˆ#,MPà ¿4AM=öxi‚ŽÓ :ÞŸ_ÈiÀš 1ðõ‘?ŽA8Å¢Ž  "<üPg'P‘®%ƒµ,A–&hyD¼4A� =^=ö°4AË,ôÜ´ì@}ðþ øBN~Ðl(ˆ�¨,Ã`Õ[»³"[gPÑ­)þ*ÀϹ³=+Èõm~X’0àgKø~b[G;bA×Ò Q�7¿4A�=?–&�‡÷gÀrðƒfCA |ðnËž÷²‚Z T�ôP¡fAþL '�Y”,à*tÿzz<¡a Çs<ËàÊ'6Øñ£Ê—Rœ8ª-kŒh^¼?¾�Ó€4 bàC3(züæ:ƒ u|0.À:4ÃA�C�)Ç@3!ô˜Ã 5ôœ4s¢?Ë´Dã³W,³ýà)ûôeOÓhàýð…œü ÙPB³¿@‘?½¢›£«³»žƒž‹žS#,I Ÿ?ÈgÀrðƒfCA |(ª)Ž_�‹pjEÍvÐcÒì=ÆpÐCÍŽÐsÐl ='=?Í¢hVä3àù øBN~Ðl(ˆ�ohñ~ü=/=?=Ï¡ˆ|ü Ÿ_ÈiÀš 1ð5Ž_€JÈgÀòð…œü ÙPßÀãø¨òðƒ||!§?h6ÄÀ×7¿�ˆ|ü Ÿ_ÈiÀš  å��ãÀ>È~�Ï€/ä4à͆‚š}àãøÀQ|�ü Ÿ_ÈiÀš  öÀÇñ €Úრàù øBN~Ðlè�ŠàS'=c½ ûÆ_?÷Ç/d?ÈgÀrðƒfC*ž�?¿|)ÁGG-´KÞæø@ムàù øBN~Ðl¨â “žI¨>È~�Ï€/ä4à͆*j9#�ã�ƒ2€ä3à 9 øA³¡Šß�¸"Ù88vÄŽ_41>È~�Ï€/ä4à͆*4Ëࣣ–5NÕ–-Wмø øA>¾�Ó€4z †Ãg¯XfúÁSöéËž¦Ñ8ÀÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðcÀ› ííí6lØ0kmm-mé4|øðl»¢­­­´µþÛ‹bàü Ÿ?ÈgÀrðc@› *òUô«Ñ7ôÿI“&eÿבzoï >Àòðƒ||!§?´Ù¨øW*þÕô³f@Ô{{o0ð~�Ï€ä3à 9 øÑ0͆¸øoiiÉšõÞÞ |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀ�†i6ÄÅhÔ{{oè…"‚ ‚ ‚ ¢XÔRŸš :xcêŽõÞÞµ~¡ òðƒ||!§?´Ù0gΜ®ÓO†âmñ¬ƒzo/Š�ðƒ|ü Ÿ_ÈiÀ�m643>Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü ÙPàù øA>¾�Ó€4 bàü Ÿ?ÈgÀrðƒfCA |€ä3àù øBN~Ðl(ˆ�ðƒ|ü Ÿ_ÈiÀš 1ð~�Ï€ä3à 9 øA³¡ >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4à͆‚ø?ÈgÀòð…œü2͆9sæØ°aòhii)m-Ž�ðƒ|ü Ÿ_ÈiÀ�!ÓlP“¡½½=û¿š j>ôàù øA>¾�Ó€C¢Ù ÆB<›A?Oš4©ôS1 |€ä3àù øBN~ ™fCÜ\hkkëõR >Àòðƒ||!§?h6ÄÀøA>~�Ï€/ä4àÇ�i6ôwÅ›o¾™½XAAAATÕеÔ�Ͳ¿ˆƒ£!› ÒÚÚÚuêËÞÎjƒ§a›  9Ñl5E³ÔÍPS4@MÑl5E³ÔÍPS.› ­­­6lØ0koo/mÐŒ”ÃÊåšßœ9s²|Ö¿š×¤I“ºÞŸ‡^Ú  ™„<ÎSN‡üîÏûµ»fƒ^05ôÑlš›ò9ä±þ¯ÐÜôþ¬\¦Ù4¯¶¶¶²CKK 9 4™�·ùfƒ¶«ž®·Ë(h6¾h0¤Ù47}x ¹La4/}ÆÎ7Ô€Ð|âfC>·û‹f€¦À·&@s‹?ÀÐlš_X¶ÜßiÖWÜlPÓ0äuˆþÔÔ44<}ˆQ³@óŠ¿ù¤Ù4?å´rY+šKœ¿zŸŽ?s«©¨ï+š Z~](€æK¢VkB ,5 㤿 €Á£÷ã ž…(ùæCoÑlаÂT.¾¨(afмÔ\ˆ å4ÍC 9å?këçPG÷7·Ý54ðé A—h^jÆù¬Ðüh6Í/~�f"Ð|òŸ³CSAïÏa[f5ˆÛ™ `pÐl5E³ÔÍPS4@MÑl5E³ÔÍPS4@MÑl5E³ÔÍPCfÿ?¾ßš…Ÿ«×ýIEND®B`‚sogou-srpc-ac65014/docs/images/benchmark5.png000066400000000000000000001505641525217424400211030ustar00rootroot00000000000000‰PNG  IHDRAiBN,µsRGB®ÎégAMA±� üa pHYsÃÃÇo¨dÑ IDATx^ìý pUwž'xÎn³3l/ÛC÷xz™-Ç´£Æ=ížvLÐÛt§#Ò›áÚeº™Lg¹iOà°# Ç�SÊUISPà°)J©V&`¬.d“`ó°ldÉCB �„@z¿gýûŸÿçÞ{îKúÿïý^Ýï'âÒï>ôøò?öýrî9ÿ…CDDDDDDD”X‚QV` BDDDDDDDY�%e– DDDDDDD”X‚QV` BDDDDDDDY�%e– DDDDDDD”X‚QV` BDDDDDDDY�%e– DDDDDDD”X‚QV` BDDDDDDDY�%AyóÍ7�×^{mt[¾|¹¾…2ÍêÕ«C²”lkkkõ­DDDD©Ç„ˆˆ`”––†¼h–�%Hæ /Ad“Œ‰ˆˆˆÒ…%Áð— AåÇùóçC^Pû·ùóç;¹¹¹�Glܸ1æcb½0—ç“Ç/\¸pôqrDƒ¼Àß»w¯¾Wlr?ùyŸ-…@aaáèÏžÈÏì/O$sù�Ëßsùûîý^ƒþîoß¾}ôöðMþþËý£åkmz�""",Aˆˆ†¼ ó^0• þÛ£mR0„¿ÐöѶ BCfáoÏ ß¿V¸ £!ü[6H¶ñç•Èýåe!™fK¹­ôoN±6yÎp‰¬M– DD„€%Áð¿�ŠW‚øo—Wò¯ÔÞmò¯Ü~A/ª–™w$ŠÇ{{‡ü)/P½¯'G�øÏÑ Ÿ‡óÿŒþïM^<Ë¿òû_ìÊãƒÞ.ëëûÉó{Ïþû+/?ù^ÃÏ×"›|?þÇÅ:×Ex¾ò8ïçò¶ðçþ߯|,¿Oÿ÷áýN‚„ ÉW~ç’¯ÿ{ zÛ‰ÿwíù…ÿ(ùZ‰ð?·ÿÿ>þ畟ɛK~DDD¨X‚Œx/¤bÝ~$ˆ_¬ÕþÛ¼~þ·É„öŸ ÿ Qï¹üG¨„“Ÿ!ü½“ÛüEˆ÷½Ë‹hi!›ðÏÂ_0ûF¯à�û„?� ÿ]Äûúÿ çðß�XyùÅú^eó޾I´ñ¿})hóAþ¿‹Ñ2“çÏ;Ö÷"¿_ÉÀûÜÿ½yüåX¬¿KþûXA)A¢}±Ö&– DD#Þ ©h·Ë‹Aÿ ÑðA�ö¢Z ÿ iïE³ÿþÑ^ &"üù½Mžßÿ}xüÅ„÷ÖyŽ ¹ðŸá›ˆö8áÿ¾¼bÅ{á,·yß_øïÖ_¦Äûú›%ˆdíÿžü_+üˆXÏ+?§w›ü¼^Á ÷ó~Wò§GæÞýe“¯%�‘ߥÿ÷åÿûãLøïXïý½õ?Þû;)ü„Ü'ï~²…ÿ¬Ñøw±þÞûŸÛãÿÙä÷LDD„Š%Áˆ÷BÊ{´M^ø{/ê=A/~½žÞ\ �ÿþ±^ &B¾Žÿùü›|¯¹Ÿ7úÙƒnó?¯¼(ö~6¯ð¿åÅÿ¢Ù?÷ŠÿQá?³ÿ÷î¿-Þ×÷ø_\{÷‰Åÿ¼‰Ü?œ÷Øðßc¬çõFá·}ÿò§7 /$ü¿_ÿ÷«”òó-âÿ;â?²(ÞÑÞýdKôwèÿ9ÃÿøùŸÛãÿ}mñJ""¢Ta BDD0ü/¤Â_ÀŠx/´ä}x"ü/~ƒ6ùWùhG8Äz1˜ ùÞý/‚ß?ÞÏæmÑÊy|ï(Ù¤hþïÃ;ÒÀÿ8ÖæQžÈ×þçŽu?O¢Ï+YËsÇÊ×/ÖóÆzÿæ•òxo&�õ‹v[¬¯ï'?—w?Ù¼¿Óþ·¸ý=÷ó?>Ö×òóçäý½ ân�ÿgÚX‚ – DD#Ö Kt»¼°—XÞÜ;²ÁÏÿâÓ¿ÉãäÅ^ø Jÿ"þý&„¿Þ‹Ãx/"½Íÿó%ò¢Ú_xxGøßöâñ¿ŽµE{;N´¯/üÏë~žDžW2ó—Ñ6¿XÏíïHøæ•eòxo&�õ‹v["?—ÇŸ›/þ·Âýç/¿LžÄÿ³ù ¹X¿"""$,AˆˆF¼RÑn—Äþ}á/0“yñ)üoGð¿Ð3ÅÿsÈ>KôEd"?Wø[bü/¦ýO"/€Ã%ú{µQ‚øß"?‡w”‹ðæ²ùÅzÞD�ÜÇ»¿<Ö/ÚmÉ|�ð·Ô$óV1–"/‘¿á¿wO¬ß– DD#Þ ©X·û_œ…ß&Ÿ{·Å{ñ)üçç�-ÖùÆÂÿsxG‚øË‰D‹—D.ÿþÇÞ[aD´óPÄ’è×·Q‚D»�bÞ\6¿XÏëÿ½$R2Èã½ûËóúE»Íÿ5ù;åÏÍÿq¼·Âù¼ûËæ»W4ñJyŽhec¬ß– DD#Þ ©X·‡þ#ä¾ÞÜÿÂ-ÿ BÙäsïŧü)/2åyc½˜•®R(ø¿¦¼�ô¿Æÿ‚ÛÿB×tƒ÷õävÿs%úsù "oó¿F?ÞÏ+·ÉÏþ˜D¿¾ÿw™Èï?‘çõÿLò±�ïÓÿ»•Í/¨„ð~ÇþÒ@~NA$™Iò}yäûòîh·… ïg“ïA~ùþüÂÿÊ–hA%ü¿Gùzþ¿kò5åw §¼‚Äÿõäc�ÜW+ÏáÝîýÎ=±~DDDHX‚Œx/¤âÝî‘ë±è1è½ðL„<‡÷¸h[¬$èþþ-ümø¿´mþï?ÑŸKž×ÿ²½˜ö¿H�¶ù%úõÇS‚mò;ú™¼Íÿ;ôó¿ÅÄ¿yâåíÿ;'?GÐ\ĺ-Ö×/A¤|¿�¿œ‰GJ¬ðR(h *Abmለõ3!a BDD0â½�ò¿ð Á(ü�—Â9â½àK”<§¼põ¥!Ï-Ïéÿ—õ ±íŬ¼ð Œ|,�‘çóKæçò¿–ï!ü¹<2—çõ òXùžä{óKôë'[‚øŸ7hóŠ'ï{õæò}Ê×ò¾žüÞÂÉmþß­ü\~’©üÝón—M>—ÇyGƈXãý=•¯áÏC¾)üÏïñ/þ¿ÓÉ�¯'߇?Sùšò³ûó�ß«w{ø&߇|�á<ñ~f""",Aˆˆ†¼ ó¿è¢‰!Ù„žðÿî """JK""‚Ádbb 2vþ£7âíCDDDñ±!""þÄÛX†d.ÿ[U¼�%Hâüçh zk%�%AñÿË·l,A2Wx "ÙF;§E’·¿x¿»X'à%""¢Ä±!""""""¢¬À„ˆˆˆˆˆˆˆ²K"""""""Ê ,Aˆˆˆˆˆˆˆ(+°!""""""¢¬À„ˆˆˆˆˆˆˆ²K"""""""Ê ,Aˆˆˆˆˆˆˆ(+°!""""""¢¬À„ˆˆˆˆˆˆˆ²K"""""""Ê ,Aˆˆˆˆˆˆˆ(+°!""""""¢¬À„ˆˆˆˆˆˆˆ²BZK�ùóç;Ë—/ןÙ“¶äµ×^söîÝË„ˆˆˆˆˆˆˆR"­G‚”––²!""""""¢”` BDDDDDDDY�%e…Œ-A®\¹â\»v�7nܸqãÆ�7nܸqã6Á¶;wîèWÿfel rôèQý!‘¿¬„‡¹`b.˜˜ &悉¹`b.˜˜ .fƒÉV.i)AjkkÕÕaü›"É` ‚‰;LÌsÁÄ\01LÌsÁÄ\p1L¶rIë‘ ãÁw ˜˜ &悉¹`b.˜˜ &悉¹àb6˜l儌âsÁÄ\01LÌsÁÄ\01\Ì“­\X‚�QÜ�`b.˜˜ &悉¹`b.˜˜ &æ‚‹Ù`²• K2Š;LÌsÁÄ\01LÌsÁÄ\p1L¶ra BFq‚‰¹`b.˜˜ &悉¹`b.˜˜ .fƒÉV.,AÈ(î@01LÌsÁÄ\01—HƒCýΣîûÎÃö;NKÓÅ´lçÏ|8ÚîÞ<îܾZl}«¯Ø²U–}œ‘Û¹’<§üàŠ1mÅ;„ΊéœÜ÷ÚîþóŒß|9Ïùfë« o_oþ±óõ¦—ǵíüôß'´mÝøRàqÛþñ ©ß~ó¯­o[=ÝÙü›ÿ9d+øOÿJïYÍb BFñ†01Lþ\ºúÛ�控îÁE§âöœÓ·ZÝN^Ûí”^øÌùCYŽóí·‹�â½ó�ý»Þt¾Ù97ðD¼íëï¶�[~œòmGÁÿ'ø?ʶ-¿þ7úãûÝöÝÇþ_Îæ Ï·ý+§pÃÿôÝöœÙmý¿p6¯ûç1·M¿úï�Oÿîé´lŸüÝ9ÚþŸ lÿÝØ·µÓœOVþ'å?N˶áƒì¬Ü>^öu6.›œòíãï¶ ß}mä-ÑÿÙùÏý_pãÆ�· Þl` BFñÅ6&æ‚EÊŽ²›_;¿ã¬>ð¦³àËÿ`mû�_ü/Î;Ÿ}ÏYµî_8¿úðŸªAÿ�áÆ�7nÙ¹}²ôÿXtÙÞþó²ÿ*#·üw';ù«þï)ß6­ú¿eüöéê)Φ_ýã”nÛrÿQFo¿ýÕíüÝë¬ÿèiçW«ÿØùèƒá,]ù}ç+þ¿Î¼e/;¯/yÅ™ó7ÿ›ó“Eo8?^8wBn6°!£øbsI¯á‘!§ên™³åôGλßÌ ,+–ì›­ ‘ %‹�mk�íg5æm[ñ_8›7þ;§àýÿÖùÏÑþ%tñ?pþ~éd§àݩΧüS§àÃ?r>ýè¿w>]óÏœO×þ±óéßý�Û¦µÿƒóÛ¿ûç)ÞþGgÛúŸG�ÔHlûžóùÇÓ¿ÛþåwÛ;;>þ£ï¶ÿÇwÛ?Ibûo¾Ûþ©óûï¶}§}·ýwÎÞ�ôÝöÏÌoùsûzó¿töö¯Ò²ûíÿäOdÛ:#Îöo�ãÛ¿?æíÐg3œS{ÿçô¾Ÿ–íÒ׳œ+ßü¯ÉmÅ/;5ÿV·Û'~æÜ>ùgÉm§æ;·Ï,×vÿÜ;N{å»NýþBý±]þÈ鸚“Öm¨ñï§©pâmv9Nëwÿc»sikàœ[z·¨¹t×8No=·4n7¯ œÛ†{õÿFªkéqŠª:9Gœ·vÖ:/m¸à<÷Ñgê;'œÿbÁ±¤·)KJ�gVžRÛók+ÔóÉ6+¿Ê™»ý*äöö—uΊâúˆíímgõoÉ,– d_lcb.é!G|ì«*ˆ(>VÿL"_œ,pÛ®;}ƒ=úc3ÔRï´üa½Sž÷ç÷3%¤ì(øëÿ“óûÅÿÐ9òþ³NÕ†9÷ö½çô\üÆj®sFº[õ3d¸¡NÇi/wœÆõßýeŸç8g§;ÎñIŽsì»ÿÄmeO9ιŽsùǹ±È}ÜÃ"Ç麤þG¥öÚýÄ„„û1LÌsÁÄ\pÙΦwpØ©lìt>?߬^Ü¿RxÙ™žsÖ™¼¨$°ÈðoRf¼�w^=FÊ‚Un9…§›œC×;—îu9õ�z�ÖžAý•&[¹|÷ƒ™‰%&îÜ11—Ôª¹_¡Žèð¹GþÒ)¹¾O�,Ï3ž\oU:Ýûsœ«þ[g÷âÐâã³Åÿ¥sôWÿι}ào�þ{ÕŽ3<¤5Aô79NóçŽs}�[x²�~Ö-:n¯yRrHa× &悉¹`b.˜˜ .“ÙÜiísöTµ¨²BŠ‹gWŸ,7¼mÚ»'�™/ª‚#ïØ§¸æ‘*Lš:úõ3f/[kæ»ÿKÌL,A0q玉¹¤†¼å%çðüÑâcYѫΗ>VG{I6¯øx”óœ ¿|Æùýâ'ç÷Øô7ÿ¥shýœúS…Îððû×9ŒT¯}ËqÎ<Yv”Lv�ì�£@䨎¶Ò„ÊŽh¸^01LÌsÁÄ\p�5›–®õV–åûë�— .9O-+ ,:äˆ9òCJ¹¯RÑÐátöM°¨2ÌÖša BFq玉¹ØuëQMHù!o9V·[]&1–Drn¿ïôÉwZsf9�Þý×Î¥_þ±³mñ5Z~l}÷¿qÎXáô÷L�·¶)0äÈ�º·Ý£9ÂK�Ò)ŽS5Ë=ÂCÞ3b¶ôázÁÄ\01LÌsÁ•h6r„Æú’Fç�m5êm*A…ÇÓï—«BdiÑMg×…NMs·~4%ËÖša BFq玉¹Ø!—µÝq.o´üxÿë •žX¹ Ô–9�;;�VÌPåGݲÿÑùò—ÿd´üø|ͳNõ‰õÎÐ`ôme”ž:÷(Ž‹3Ý#;ü¥Ç‰©ŽS=Ûqîä9NG…ñÒ#× &悉¹`b.˜˜ ® lä<GëZ�5‡o«“ŠÊ Gà 9y©Ü&GwÈ[`äÈ2ÇÖša BFq玉¹˜w¡±D½ÝEÊ�E{ìì¿üYÂå‡'<—‘Á~§÷Ô§-ïOUñ![ÓûÿÆ9¸æ_=9òãýiªü˜oy‘âãÖªàózœÁqêW¸Gz¤× &悉¹`b.˜˜ .ÉfpxD•râR¹šJÐIKåür5“OÊ’’]¶Ö K2Š;wLÌÅ9ú£ðÔêÑ£?òO,sZ:ïê[“ãå2ÜùÐé9ü±óxÍŸŒ–R„\ØúgNá²ÿZ•Ÿ.™<1Þö­ø�·¸ÈILåÜrâÓ4âzÁÄ\01LÌsÁ#WV‘BcæoNé!çñX°çº:‡OTšz¶Ö K2Š;wLÌÅŒ«Õ[^¤üX²o¶Svók}ËØÔ^:ït›ç<þà{£åGû§o:mç¾pöÿý=úãëü™N{K�~Thqœ†Ç©x>´ø�KÕÊÉLǼ~~ªq½`b.˜˜ &悉¹¤Ÿ¼ÅE®¾"Wbyî£3¥‡Ì¤ô�·¶LÔËÎf[k†%Å�;&æ2>Ã#CΑÚ]ÎÂÝ?THÞÑ_„\ê6YC-õê*/W½8Z~tn_è 6T9õ—ö¨·¼HùQ¸|ªsíL¡~T†‘“›Þßê8—^vœã“B�ø¸:×q²~n�±âzÁÄ\01LÌsI=¹úJé�6uéÙ9[®Dí!ŸË[Vï­T—¶%,¶Ö K2Š;wLÌe쪛N9«¼9úö9÷‡”"cÑåˆÓñÛ¿->¼òc¨éª*?¾È�>zôGÑÆ—œ®¶;ú‘BŽæ�·³„ŸÜTJ)Cš?‡:â#®LÌsÁÄ\01ûäê-òö–y;®©·²LZx<¤ô�íùµêÊ-rþ9ˆ`6˜l儌âsIÞ½özgcéÒÑòCŠ�ª»eúÖ$ 9}ç÷9m¿ùÉhññxÕ÷�®¯>tnœ:àܽ~ÔÙ·áÅÑòcû‡ÏdÞÑÝ5Žsc‘{ñ!eˆ\Ñ%ÍçøH× &悉¹`b.˜˜‹Yr”Ç¡k��Un©+´È•Z )AfäžSo)(¿çÔµôèG‡b6˜l儌âsIœåqøêŽÑ·¾¼óÕOÕçÉ^ùEôW}ë´æþh´ü�"¤ïÌ.g¤¿[åñź?-?ä-0U%y™uÉ[¹\­\ºÖ+>d“«ºÈÑ r� Åõ‚‰¹`b.˜˜ &æ2>Þ%kå(ŽòÎåá]½e}I£SÑС“fƒÉV.,AÈ(î@01—Ätô>9úcǹ<5KÖ`ãeu‚S¯ühÏ]½FÈ¥m¥ìØüË)ªü�ó~ÈU_ú:Õí¡³Òq.¼ô¤ø�·¾Ô¾å8]—ô2× &悉¹`b.˜˜Krä­*å·Ú£^²VJ)Cí»¡Nd:ž«·0L¶ra BFq‚‰¹ÄWs¿ÂYVôª*?ÞýfŽú9Á©wÅùS>Ï\/˜˜ &悉¹`ÊÖ\äh9’C.[ûôûå!¥‡lr^9ÿ#]¸f0ÙÊ…%Å&æJ ��Î=ÿGcÛu}Kbäm.Ýûsžý±ùçÎpë]}«ëÜ¡UNÁâIªù:?øÜ°¹„ýqù•Œ»Ìíxp½`b.˜˜ &æ‚)›rñÎíëh�­÷�–®ýˆôâšÁd+– dw ˜˜ËþDþLöê/C-õNÛÇsÜdÅ §÷Ä}‹K®òrhË+£o‘+¿D™‹ÿè9ˆœ $Ëp½`b.˜˜ &æ‚i"ç"—¢=tí±ºRKй=¼£=ä²µˆ¸f0ÙÊ…%Å&æâ’Âcõ�7U²îØÂ¤O€Úwv·zÛ‹ m¿ù‰3ÔtUßâ’óìÊy^•r ÜÛ—‹ô-Á r :÷Ç»êK¢¸^01LÌsÁ4Ñr‘£=>)»ë¼\p)âJ.Sß9áÌÙrE�Ûåh�X¸f0ÙÊ…%Å&æzˆ }ƒ=ú–øFz;œÎ�KGßþÒõÕ‡ÎH·¾Õõ¨é’³õýiªùýÚçœÖæ}Kt0¹dé¹?¢ázÁÄ\01LÌS¦çâ]Éåí/ë�öx~m…º|mé�6gpxD?*3pÍ`²• K2Š;LÙžËx �ÁÆËNkî�Tù!—Àí¿rDßòÄÝëGÕ‘R€ìÛðâèÕ_âI{.½õŽsá¥'Gȹ?äˆ�,Çý&悉¹`b.˜21—šængÍáÛ�çöðŽö((¿çÜiíÓ�ÈL\3˜l儌âS6ç2<24z˜d yû‹œ÷C �öOß ¹ô­§éféhrxëgx8ñkا59Ú£tŠ[~œœ–•çþˆ†û1LÌsÁÄ\0eB.Þ¹=ì¹x´ÇŒÜsÎòýõy´G,\3˜l儌âS6ç²çb¾*@VÿÌéèÔÓ8†‡œî¯ÿvôí/=‡?V³pþääÞzš¸´ä"çù�#>¼£?jÞpœÁÄŽ\ÉÜ�ab.˜˜ &æ‚ 5—¦Ž~uîŽW /žÛíJ.6pÍ`²• K2Š;LÙšËù;ÇT²p÷�†Ç‰ýä\rÉ[õö—Ußwú«¾Õ·„o"RžK[©ã”?í–rî�æÏõ äÇý&悉¹`b.˜�r¹t¯ËYuà–ºj‹¿ô�í¹�Ψ«¼­k�PG{ÄÂ5ƒÉV.,AÈ(î@0ec.­=-Î;_ýT• %×÷éilÃ��¶õ¯ª¤5g–:H�ö–ºÑ“ žØý¶ž&/¥¹Ü+pœã“ܤòE÷| ˆû1LÌsÁÄ\0¥3)3ä-,Aos™´ð¸:çGÞ±;êŠ/Ùˆk“­\X‚�QÜ�`ÊÆ\ ÊÞSˆü™ˆ¡–zuÙ[)@äO)D‚ÈIOåê/R€oz9©s€„KI.#ß}uo?yûËõâ~ sÁÄ\01L©ÎEÎï±§ªÅ™»ýªóÔ²²�âCÞöâ½ÍE®ú’í¸f0ÙÊ…%ŦlËåô­ƒª‘#Aäˆ�x†58­kgª¤=ÿõ¨ˆE_RȹÓ��¾Ï1…õ\äügºåGÉd÷d¨÷c˜˜ &悉¹`JE.^ñ!Gøù=ž~¿\]ÞVN|š-osI× &[¹°!£¸Á”M¹øß#eH<ê-0ú¹Œœ$š£;æªDÞ ÓÝѤ§cg5—®KŽsúY·‘«¿´—ë(îÇ01LÌsÁd+)4ŠkÓsÎ:+Šë�ÊÆñýÃÍDÇ5ƒÉV.,AÈ(î@0eS.ÞÛ` O­Ö“è¤ð�#?Ô[`Ö¿³9wh•*@äd¨ïVêéøXËåaÑ“Ëßž›á8ýã/l² ÷c˜˜ &悉¹`2�‹œ¸tÞŽkou‘âCNzš­ç÷ ®L¶ra BFq‚)[rñ®³¬èU§«¿]O£ëܹ4î9@ÄõÊÏUR°x’sûr‘žŽŸ•\î~òä¨rùÛá^}%Šû1LÌsÁÄ\0™ÈE.g»æðmuæpÍ`²• K2Š;LÙ�Kß`�óþþ×U Rqûz]ß™]ªyüáœáÖ»zIŽúð.…[}b½žša<—ÛkÜòC6ù˜Æ„û1LÌsÁÄ\0�5y»KQõCçå‚KêJ.^ñ!çø�·º°ø?®L¶ra BFq‚)rÙWU  �uÇêItCÍuÎã¾§J�þ+Gô4’œ÷cÛʧUr|ç<=5Çh.rÕ)?ä(¹.�÷c˜˜ &悉¹`J69êcùþzgÚ»'G‹�É‹Jœ9[®¨s€�9\3˜l儌âÓDÏå^{½³p÷Õ&ÇÓ±ùçªéÞŸ£'‘äJ0{Ö½  �}^×¥p£1–‹¿y°Ki¬¸ÃÄ\01LÌS¢¹”ßjWE‡ÿ¨�¹çœõ%�Nk/so× &[¹°!£¸Á4Ñs‘£?ä(��ç×éItýՇܷÁ¬ùg¤·CO#yW‚Ùþá3F®ÄH.,@Œã~ sÁÄ\01L±r‘KÛn­¸¯ÊÿQrÅ^ÙÅ>®L¶ra BFq‚i"ç"çÿðN†Ú3熇œ¶¼?U%Hï©zIÎý!ˆÉ+Áw. 9,@,à~ sÁÄ\01LA¹È‘r¢Sÿ[^äc9ׇ¼†Rƒk“­\X‚�QÜ�`š¨¹ õ;+‹¦J�òúb=�Î; DŠ)D‚= 54Øë|‘;] ¥_¼¥§öŒ9—¶ïöÃR~H r'OÉîÇ01LÌsÁ$¹È[[^)¼r¾�YùUΡk�õ½(¸f0ÙÊ%n "Enn®úX )DÂÉí^‘±wïÞÑûD›Ël,ŇKLÜ�`šˆ¹È[_Þùꧪ©¹_¡§ÑÉU`ârb÷ÛªÙ•ó¼3Ðgÿý·cÊ¥»æ»�ñ·¹±HÉ$îÇ01LÌsÁs´®Õ™ù›S£Å‡” s·_åù>@pÍ`²•KÜDŠ ÿÑò¹ÿH>“£;D´9K�‰‹;L1—=óU²¡d±žÄ ç‰sÈíËEª‘·ÂØ<ˆ_Ò¹ô79NùÓnrù=$Ó¸ÃÄ\01L̇”/m¸0Z~ÈÉNåm0<ß®L¶rI¨ñË—/�xKŒÌäH�÷˜hs)@üo‡‰ö›XX‚`âÓDË¥¥ó®º®” �m×õ4:)>b"WÙòÞSª‘“¢¦JR¹ u:NÅónRù¢ãŒðy¶p?†‰¹`b.˜˜Kúí©jq^È;?Z~L}ç„óö¶³¼Ä-(®L¶r1R‚ÈçþRC6yL´¹Ÿÿm2É�D~)ܸq˾-÷ÀU€äy/ðvÿV{é¼ÓòáKª¹µÿ?ÞgÏÆ©äËõ3oGغNý‰*@úOü±SWSxnܸqãÆ�[z·�¿½èüËÕe£åÇSï”8óûsNEUMàý¹qã{³ÁÈÛaÂE+5¢Í¥I�Ádë/*�ÏDÊ¥ên™*@ä| ½ñO"Ö½?G íÿ3= ÕPS¬ ¹nWÛ=M�„s©_îRö”ãôÔé!ÙÂý&悉¹`b.©'G~ÈÕ]¼òC.s›wìNÈ•^˜ .fƒÉV.qK9¹i¼£úùO†êm.G‚ÈÑ%Éb ‚‰;L%ÿ%qËn~­§Ñ 5×9�VÌP›|N®³ýÃgT RU’ú«¬$”Ëý­n"Wƒiå~/¸ÃÄ\01LÌ%uÂË�gVžŠ(?<̳Ád+—¸%ˆz;‹^y!‡w»W˜ˆhsyœ7—m,X‚`âÓDÉeÿåÏT’{ä/º$nû§oª£@äh� §¿Yª �Ý¿žá §þ=ºqsé¨xr%˜»Ÿè!ÙÆý&悉¹`b.ö…—Ï®>ížnr‡Gô="1\Ì“­\*A±ÁĦ‰�‹œ uÑÞ«¢6<Žÿóô_øF ­9³œ‘þn=}B®S°x’Úšo•ëijÅÌE®srš[€\›§‡” Ü�ab.˜˜ &æbÏ® ’.?<̳Ád+– dw ˜&B.›Ê?PG�|yác=‰nd°ßi];S• ýÕ‡ô4TÑÆ—ÔQ 'v¿­'©3—‹3ÝäÂK¼LŠq?†‰¹`b.˜˜‹yr©Û¹çÆT~x˜ .fƒÉV.,AÈ(î@0ez.u.Žž µg SO£ë9’¯ �ŽÍ?דPw¯UHáò©NO«ž¦^Ô\n¯q 9d E)U¸ÃÄ\01LÌÅœK÷ºœ— .…”[+î'U~x˜ .fƒÉV.,AÈ(î@0ez.r)AŽÔîÒ“è†;:�W}_• ƒ�—õ4Ô¾ /ªäì�z’�¹Èy@ä$¨R‚<*ÖCJ%îÇ01LÌs¿¦Ž~gîö«Î¤…ÇUù1õ�NΑ†Àž&йàb6˜l儌âS&çrúÖAU€ÈUaäê0ñtí^¡ �Î�Kõ$Tý¥=ªÙúþ´´""rêüî~Ö-@®/ÐCJ5îÇ01LÌs»ÖžAgùþzgò¢U~ÈŸ ö\Wóñb.¸˜ &[¹°!£¸Á”©¹Héñþþ×U "eHN=ã– wÒ„J¶ã~ sÁÄ\01—ÄÈy?ÞØV3Z~ÈIO÷TÙ;!9sÁÅl0ÙÊ…%ŦLÌÅ»"Œ¼fxdHO£ë>¸.æ¹@Z›kT²ù—SÒ~.Ïh.õËݤây^÷c˜˜ &悉¹Äæ½õENv*凜÷cÕ�[ã:éi"˜ .fƒÉV.,AÈ(î@0eb.[N¤J�5Ûõ$¶ÖœYîaªô$Ô‰Ýo«DþD¡ré®yr5˜¶R} ¥÷c˜˜ &悉¹DWÓÜí¼�w~ôè�Ù›ª�úG©y‹,sÁÅl0ÙÊ…%ŦLË¥«¿ÝY´÷ÇÎÂÝ?t:zëit7+TÒ–÷§zJÎÿQ¸|ª*A5áœoCårq¦[€\�«§”nÜ�ab.˜˜ &æIŽþ�KÜzW}yúýr§¨ú¡¾55˜ .fƒÉV.,AÈ(î@0eZ.Gjw©£@ O­Ö“غö®T%HÏ‘|= uùä'ª‘s‚ ¹wá?¹ȉ©Ž3`ï=È”îÇ01LÌs ~ôÇ[;k�\ò6Y̳Ád+– dw ˜2-5÷+ô$º‘Á~çñš?Q%H´ËâîÛð¢*An\Ü¥'F��ÿÌ-Aî~¢‡„€û1LÌsÁÄ\\áG<³ò”s´.}çc.¸˜ &[¹°!£¸Á”I¹4<¾–Ô Qû¯QH{þëzªãQýè Q.‹;ªq½[€œyŽ'CÃý&悉¹`b.Ž:χ\êÖ;úãí/ëœÎ¾øÿ_asÁÅl0ÙÊ…%ŦLÊåË «d_U�žÄÖ¹c±*AzOþNOB�;´J• G¶¿¡'[çä4·yX¤‡„‚û1LÌsÁ”í¹¬/it¦,)…8úÃ�ë³Ád+– dw ˜2%9òcYÑ«ªil»®§Ñ ·ßw­˜¡¶áÎà“›ý~ísªi¬=¤'êW¨¤ûä¿ÓBÂý&悉¹`ÊÖ\�þðãzÁÅl0ÙÊ…%ŦLÉ¥ên™*@Öús=‰­çðÇê(�Î�Kõ$Ôû•ªÙòÞSÎð0È[Nú›§tŠ*AnW©‡„„û1LÌsÁ”�¹ ýáÇõ‚‹Ù`²• K2Š;L™’˦òT røê=‰n¤·ÃyüáT 2x«ROC�Ü»@• ¥_¼¥'jÞpßóÝŸ\/˜˜ &悉¹`ʦ\.Ýë ¹ò ÚÑ~\/¸˜ &[¹°!£¸Á” ¹tô>vîþ¡ÚZ{â_.¶{Ž*@:> .8ä$¨rˆ” â_e&%ÚJÝDŽé»Ãõй`b.˜˜ ¦lÈ¥wpØYZtÓ™´ð¸*?ž]}òè?®\Ì“­\X‚�QÜ�`Ê„\ŽÔîRG�”½§'Ñ µÔ�ž d¨¹NOCÉåp¥Ù•󼞤™\¦â»ïEJ�ÛkÔˆësÁÄ\01L=—âšGê-/R~H "eˆ”"è¸^p1L¶ra BFq‚)rùèà�úÎ U~ÈŸòy¦ázÁÅl0ÙÊ…%Å&ô\vž_§J�=óõ$˜\×;jÕ·z©òkÔQ ‡·ÎÑ“4»:×-@®„~?\/˜˜ &悉¹`šH¹Ô4w‡œøTŽ‘#B2× .fƒÉV.,AÈ(î@0!ç28Ôï,Ù7[• ÷Úëõ4XçŽÅîÉPûzìó5Ϫ¤±öÉQiÓYé8Ç'9NÉdu2T?®LÌsÁÄ\0M„\äè�5‡o;“•¨òãé÷Ë�¢ê‡úÖÌÄõ‚‹Ù`²• K2Š;Lȹœ¾uP ¹GþRO‚ Ô–©äñªï;íwõ4ÒÝëGU²ýÃgô$Í.Ît�¹¹TžàzÁÄ\01LÌS¦çRÙØéLÏ9;zôÇÜíW�Ö÷|Z™Œë³Ád+– dw ˜�sÙP²X• e7¿Ö“`mÏQ%HoÉf= vtÇ\U‚œ=°BOÒèQ±[€”=å8ƒ‘—íãzÁÄ\01LÌS¦æ"Gäi˜PGøq½àb6˜l儌âj.�ºï«dÑÞ;=�z©ÿÊ‘''Cì×ÓH}�Îæ_NQ'E•“£¦•ÿ’¸re˜\/˜˜ &悉¹`ÊÄ\êõ:/m¸0áŽþðãzÁÅl0ÙÊ…%Å&Ô\Šª7«dËé�ô$X{þëîQ '§'Á.ŸüD"—ÇM»{nrúÙÑKâ†ãzÁÄ\01LÌS¦å"G{LYRªÊ�§–•9».<зL,\/¸˜ &[¹°!£¸Á„˜‹\ ÷Ýo樤îÁE=�$W„ñÎë(±û×3T rãâ.=I#ï(�¦B=ˆÄõ‚‰¹`b.˜˜ ¦LÊeEqýèѳò«2öÊ/‰àzÁÅl0ÙÊ…%Å&Ä\.4–¨䣃óô$Xßù}ª‘+ÃÄò ¡B [ߟæ §ùðÙör·99Íq†{õ0× &悉¹`b.˜2!—ξ!u¹[¯Y¾¿^�d"ãzÁÅl0ÙÊ…%Å&Ä\òO,S%ȱºÝz¬kïJU‚ô��}?§¿‰¼ KÊ]�ë– W„ñãzÁÄ\01LÌz.-]΋ë*Uù!oƒ™¨o Çõ‚‹Ù`²• K2Š;Lh¹$zBTá�d°ñ²žDêïiu>]2Y• �êõ4MZ§d²[‚ôÆþ^¸^01LÌsÁ„œKSG¿óÜGgT2íݓΥ{]ú–‰�ë³Ád+– dw ˜ÐrñNˆºã\ðUSüä\ R‚ŒôvèI¤‹GspNˆÚ�ã —^Öƒè¸^01LÌsÁ„šKMs÷h"J!’M¸^p1L¶ra BFq‚ -—÷÷¿®J�†Ç±¿¯Ñ“¢®ù= ¶+çy¼¢>,Òƒè¸^01LÌsÁ„˜‹ñ!G~H"o…ɶDp½àb6˜l儌âR.ךϫdí¡?דèªT "o‰‰æáÝJU€lyï)gh0úIHS¢³Ò-@ÊžŠzY\?®LÌsÁÄ\0¡åRz£mô¸/m¸ NŠš�¸^p1L¶ra BFq‚ )—mkU røê=‰®¿ê[U‚tîŒ~‚Ñò¢Eª9±ûm=I£ë ÜDþL× &悉¹`b.˜�rÙZqß™¼¨D r5˜‰~˜X¸^p1L¶ra BFq‚ %—¾Áu2T)AZ{Zô4ºîƒëT Òs¬@OBÉ¥p·­|Z• r‰Ü´’#?ä’¸R‚È%rÀõ‚‰¹`b.˜˜ &„\¤ì�ËÞJù!ÛÜíW³º\/¸˜ &[¹°!£¸Á„’Ky}±*@6”,Ö“Ø:6ÿ\• WKô$Tcí!U€È9AÒîQ±[€œ~VâãzÁÄ\01LÌSºs‘·»¼\pI•“wÖ¾­oÉn\/¸˜ &[¹°!£¸Á„’ËÆÒ¥ª‘2$ž‘þnçñßS%ÈHw«ž†:¾sž*Aäê0iweŽ[‚ÜZ¥ñq½`b.˜˜ &æ‚)�¹Èù?žYyJ r�¢ê‡úâzÁÅl0ÙÊ…%Å&”\Þùê§ ¿¦ÿÂ7îIQ?}SO"yW…‘“£¦ÕÀw?OÉd·é­×Ãø¸^01LÌsÁ”Ž\ä­.K‹nª#?¤‘KàÊaè ®\Ì“­\X‚�QÜ�`BÈ¥¥ó®*@ÞýfŽžÄÖ¹}¡*AzOþNOBõ÷´ªdó/§èI5®w �‹3õ 1\/˜˜ &悉¹`Ju.•��Îôœ³£oY´ï†Ó;8¬o%× .fƒÉV.,AÈ(î@0!ärþÎ1U‚l*ÿ@O¢î|è�¢�/éI�›á– ÍŸëAb¸^01LÌsÁ”ª\äÜRxxGÈÛ`äí0Œë³Ád+– dw ˜rÙs1_• GjwéIt=‡?VG�tlû+=‰T}b½*AJ¿xKOÒ¤³Ò-@NLuœá^=L × &悉¹`b.˜R‘‹œëÃ;÷‡” ö\W¥EÇõ‚‹Ù`²• K2Š;L¹¬;¶P• u.êI°‘Á~§uíL÷ª0µezéÄî·1NŠZ÷¶[‚ÈŸIâzÁÄ\01LÌ“Í\š:ú�W /«òC¶¹çœŠ†}+ÅÂõ‚‹Ù`²• K2Š;L¹x'EíèÔ“`ýUߪ¤í7?Ñ“`ò6)Aê/íÑ“4�#?ÊžrK�Ž =L× &悉¹`b.˜låRP~Ï™úÎ U~È•_òŽÝQ'D¥Äp½àb6˜lå2®dùòåÎk¯½¦¶½{÷êi¨ùóç�Þ§´´TO£ÏÅw ˜Ò�Ë£îû ŸµýïæžõÔ= ¶å½§T ÒÚ\£'ið`—[€œ�®ÉázÁÄ\01LÌ“é\jš»�×UŽýñrÁ%çNkŸ¾•Åõ‚‹Ù`²•˘K)=¤µµµªÌWXXèäææª�¥èðîmž – ˜¸Á”î\ª›N©$ÿÄ2= 6ÔtU �?ü�3Òß­§‘z»ZTòé’Éz’&U³ÜD®3\/˜˜ &悉¹`2•‹屿ðmgò¢U~<ý~¹³ëÂ}+%‹ë³Ád+—1— R€ø�þ�ÏÃ�èŸÉÑR˜D›'ƒ%&î@0¥;—ÃWw¨DNŽ‹wBÔ®½+õ$˜¼FJ�}^Ô“4è»ã8Ç'9NÉdÇhÑÃäp½`b.˜˜ &æ‚ÉD.þËÞÊöÖÎZ§µgPßJcÁõ‚‹Ù`²•˸J‘!Gv„¿%FfrÔ‡Ç{L´y2X‚`âSºsÙrú#U‚”ÝüZO‚µåý©{BÔë§ô$Øéo–ª¤¼h‘ž¤Á­UîQ Wâ¿Å'®LÌsÁÄ\0�'9úcùþú�ËÞ­kÕ·Òxp½àb6˜låbµñÞ&ãßä1ÑæÉ�D~)ܸqÃßV~=W• %•o—íúùªy¸êE§öjMà}¼íwýKU‚œ<˜x{*¶�Ò?R%HcÕ§�·sãÆ�7n™¶¤J�Žßþ…žko©SÈæ_Nq†{õ4ÅZ¿ÛÿÈQ åOëÁØp½`b.˜˜ &æ‚i,¹|RvW]ñEÊ9÷�þ0�ë³Ád+—1— òv)>D´£úùO†êmKLÜ�`Jg. �¯©䣃óô$X÷þU‚ô‰}Þ�Šâåª9²ý =Iy Œ” õ+ô`l¸^01LÌsÁ”L.-]ÎìMÕ£GÌÙr…çþ°„ë³Ád+—1— "è2·rtˆwd‡Þí^a"¢Í“Áw ˜Ò™‹œDJ�ÂS«õ$˜wiÜ�«%zIŽüØúþ4U‚4ÖÒÓluO†*%ˆœu¸^01LÌsÁ”h.Å5�ÔQR~ÈQ [+îë[È®\Ì“­\ÆU‚¤KLÜ�`Jg._^øX• GjwéI¤‘Á~çñßS%ÈHo‡žFª=»U _äNד4�ËáJ"—Ç'®LÌsÁÄ\0ÅËEN~º`ÏõÑ£?^\WéÔ?JÓ[L³× .fƒÉV.,AÈ(î@0¥3—¼£¿P%ȵæózi°ñ²*@Ú~ó= ¶gÝ ª¹væÉÕ¥Rîìt·y½ÔI× &悉¹`b.˜bå"eÇŒÜsªü�+À¬:pK•"d× .fƒÉV.,AÈ(î@0¥3—%ûf«¤g SO"õ�ß§J�΋õ$Òƒ† U€lyï©ô�µ£Â-@Êžrœáñ\/˜˜ &悉¹`Š–Ëžªgê;'T"—¾-¿Õ®o¡TàzÁÅl0ÙÊ…%ŦtåÒÜÑ  �÷÷¿®'Áº¿Í‹{RÔã;穤¼h‘ž¤AÝÛn rÃÌ÷Àõ‚‰¹`b.˜˜ ¦ð\äH�EûnŒ¾ýEN„Ê“Ÿ¦× .fƒÉV.,AÈ(î@0¥+— �%ªÙTþ�žëØöWªé¿rDOBÉ‘rI\)AZ›kô4ÅäÈ�SÝ¤ë’Ž× &悉¹`b.˜ü¹ÈÕ_fn¼8úö—¼cã;©7�× .fƒÉV.,AÈ(î@0¥+—¢êͪ9P³]O‚µæþH• C-õzªþÒU€È9AÒæþV·9oî{àzÁÄ\01LÌ“—Ë¥{]γ«O«dÚ»'ùö—4ãzÁÅl0ÙÊ…%ŦtåRPöž*Aª›NéI¤‘þnU€ÈÕa¢9¼uŽ*AªO¬×“4¸ð’[‚Ü+ЃñãzÁÄ\01LÌ“äRz£M]öV 9jSG¿¾•Ò…ë³Ád+– dw ˜Ò•˲¢WU ÒÑûXO"yW†iÏ�~Þ�m+ŸV%HW[šáí®q �Ò)Ž3ý¯ÉâzÁÄ\01LÌSþ�‹£ÈÜíW�ÞÁa} ¥× .fƒÉV.,AÈ(î@0¥#)>¤‘"$–ÞS;T ÒõÕ‡zJŠ)@¤I9ª” µoé�\/˜˜ &悉¹àÙuá�óþ£{Ô{®ë)!àzÁÅl0ÙÊ…%Ŧtä"o�‘DÞK×Þ•ª‘2$È�‹»T rhË+z’brBT¹$®Á¢z¸^01LÌsÁRTýP�ü”&®\Ì“­\X‚�QÜ�`JG.r2T)Aö_þLO‚µ}÷0L̳Ád+– dw ˜R�Ë‘Ú]ªÙWU 'ÁÚÿþgª¸|Þ9ª” rrÔ”ë¨p �SöŽBázÁÄ\01LÌ%=¤ðxq]¥*@ä­0rDˆsÁÄ\p1L¶ra BFq‚)Õ¹žZ­J�ŠÛГ`�W}_• Ã�õ$ÔÑsU ríŒù“’Æu}�[‚Ô/×ó¸^01LÌsI½ð¤¦9òÄâÌsÁÅl0ÙÊ…%ŦTçòÑÁyª¹×^¯'‘†šëTòxÍŸèI¤Ý¿ž¡J� z’BåO»%HO�˜Çõ‚‰¹`b.˜˜Kjµt Ä-@sÁÄ\p1L¶ra BFq‚)•¹ô ö¨dɾÙz¬¿ê[U‚tlû+= 5<<è|ºd²*Aú:õ4Eú›Üää4=°ƒësÁÄ\01—Ô¹ÓÚ7zÔgWŸŽZ€悉¹àb6˜l儌âS*s¹ñ°Z• ¹GþRO‚u›§J�žÃëI¨‡w+U²+çy=I¡–=n R»È/®LÌsÁÄ\R£¢¡cô2¸r2Ôðs€„c.˜˜ .fƒÉV.,AÈ(î@0¥2—cu»U òå…àrÃÓñÛ¿P%Hõ!= U{v«*AŽlCORèÆ"·iÈÑ;¸^01LÌs±ïóóÍΔ%¥ª™½©:ä*0Ñ0L̳Ád+– dw ˜R™Ë¶Šµª)¹¾OO‚ɹ@¤zÔ '¡Nî] J�ª’<=I¡ /¹%H«Ýý × &悉¹`b.v-ß_¯ÊÙÞþ²ÎÑ·ÄÆ\01\Ì“­\X‚�QÜ�`Je.ký¹*AGÿšÃí÷Ý“¢~ø=‰´o˪iºYª')T:ÅqŽOrœ!»ç"ázÁÄ\01LÌÅ9Úã•Â˪ü˜´ð¸³¾¤Qß’悉¹àb6˜l儌âSªrêwîþ¡*Aäãh®–¸'Eýì-=‰´ù—ST ÒßÓª')Ò]ãræ9=°‡ësÁÄ\01óä|rÞ)@äm0EÕÁ—‘�…¹`b.¸˜ &[¹°!£¸Á”ª\äè)@äh�XzލDNŽäQÓ%U€|¾æY=I¡�Up½`b.˜˜ &æb–\ñ噕§T"^º×¥oIsÁÄ\p1L¶ra BFq‚)U¹Èy@¤Ùrú#= Ö¹c±{RÔªoõ$Ô�‹»T rhË+z’B·×¸%ÈÍ¥z`× &悉¹`b.æ”Þhs¦¾sB /m¸à´t è[’Ç\01\Ì“­\X‚�QÜ�`JU.;Îå©äHí.= Ö–÷§îIQ›ëô$TyÑ"U‚\ž£'‘¶øŒ*AÞM͉IG ÷:Né·é³ÿ^m× &悉¹`b.Áš:ú�ç>:£ �™/¦´ÌsÁÅl0ÙÊ…%Å&›¹ô tªD®KßÙݪéüb™ž„êïiUHÁâIÎÐ`¯ž¦ˆw>�³Óõ 5¸^01LÌs‰ä/@^\Wétö_Þ&悉¹àb6˜l儌â“Í\®5ŸW%ÈÆÒ¥z¬ûë¿uOŠzòwzêîõ£ªù"7µE„Ró†[‚ÜZ¥©Áõ‚‰¹`b.˜˜K(„D0L̳Ád+– dw ˜lær¤v—*Aö\Ìד`퟾©J��ë§ô$TUIž*AŽïœ§')2Øê^WJ�ž:=L ®LÌsÁÄ\ž�Âãùµª‘"¤¥k@ß’zÌsÁÅl0ÙÊ…%Å&›¹žZ­J�ŠÛГ`�W}_• #½zê莹ª©>±^OR¤!Ç-@.¼¤©Ãõ‚‰¹`b.˜˜‹K 9òÃ+@äˆ�tb.˜˜ .fƒÉV.,AÈ(î@0ÙÌeí¡?W%Ƚöz=‰4Ô\§ �ÖœYzIÞ#%Hó­r=I�‘AÇ9õŒ[‚¤ðª0®LÌsÁÄ\ð Á\01\Ì“­\X‚�QÜ�`²•Kß`�*@–ì›­'Áú«¾uOŠº}¡ž„’¡Ê Q¥èëÔÓxXä åO»…HŠq½`b.˜˜ ¦lÏÅ_€<³òD"¸^01\Ì“­\*AæÏŸï¼öÚkj+--ÕÓPÞí²ÕÖÖêiè¼°°PÍäÏ y2X‚`â“­\n<¬V%Hî‘¿Ô“`Ýßæ©¤çHðyCä’¸R€ìÊy^OR¤j–[‚Ü^£©Åõ‚‰¹`b.˜²9¹ìíìMÕª™öîI§¦¹[ß’~\/˜˜ .fƒÉV.qK)(rssÕÇR€H!Nn÷ŠŒ½{÷ŽÞÇ?—bD !³±~,A0q‚ÉV.Çêv«äË ëI°ŽÏÞR%Hÿ•#zªæT�*AŽlCOR@N‚*ˆœu ES‹ësÁÄ\0ek.R€¼Rx²\/˜˜ .fƒÉV.qK)4üGÈçþ#=Dø,¨ì�Û½r„%ÈÄÅ&[¹ì8—§J�òúb= öøÃ¨d¸õ®ž„:¹w�*A.ÍÑ“�ËáJ "—ÇM®LÌsÁ”�¹  ‚ësÁÅl0ÙÊ%¡Ä_p,_¾<â-12“#@<þÇÈÇÞÛ^6rix|M"GƒÄÒsøcU‚tï~«K{K�*@>_󬞤€ý!G�\xIÒƒësÁÄ\0eS. ö\=¤ôF›žbâzÁÄ\p1L¶r‰[‚ÈÑñNŒêç?ªÜ×ÿ6ï?¹]Ê’d±ÁÄ&¹”ÝüZ• ÛÏþJO‚ÉeqÕIQ/|£'¡n\Ü¥J�C[^Ñ“¸¹Ô-AäÏ4âzÁÄ\01LÙ’Ëòýõª™´ð¸sèÚc=ÅÅõ‚‰¹àb6˜lå·R^x›WbHÑá•R|x·ûßòâ½ ÆÛ¼rDçŸ�KLÜ�`²‘‹\FJ¹BL,­9³T 2Ô\§'¡Ê‹©äÜ¡Uz’rHšÏ"¸^01LÌS6ä’wìÎh²ëÂ=ÅÆõ‚‰¹àb6˜lå’P ‚ˆ%&î@0ÙÈ%÷È_ª¤îÁE=‰4ÒÛážuÕ÷õ$Òþ‚Yª¹}¹HO,tœÒ)n Òߤ‡éÁõ‚‰¹`b.˜&z.™X€®L̳Ád+– dw ˜Lç2<2ä,ÚûcU‚ô têi¤�ë§T ÒžÿºžDÚòÞSªéíjÑË:+ÝäÔ3z�>\/˜˜ &æ‚i"çòIÙ]U€ÈVx:½¥y²¸^01\Ì“­\X‚�QÜ�`2�˽özU€¬>ð¦žë=±Å=)ê׫'¡ºÚî¨dëûÓô$š?wK�êÙz�>\/˜˜ &æ‚i¢æ"G}ÈÑR€ÈÑ ™†ësÁÅl0ÙÊ…%Å&Ó¹TÜþƒ*A6• 'Á:¿X¦J�¾³Áç ©¿´G• Å›^Ö“ðNŠZŸü ™MãzÁÄ\01L1—L/@× &æ‚‹Ù`²• K2Š;L¦sÙs1_• j¶ëI°¶õ¯º'Emºª'¡ÎX¡J�Šârˆ” véAúp½`b.˜˜ ¦‰–KQõÃÑdÕ�[zšy¸^01\Ì“­\X‚�QÜ�`2�˺c U Ru·LO"� ö;�VÌP›|ä@álU‚È!)sæ9·麤éÃõ‚‰¹`b.˜&R.¥7Úœ)KJU²`Ïu=ÍL\/˜˜ .fƒÉV.,AÈ(î@0™Îeɾ٪ií‰~2ÓÁÆËê(�¶�çèI¤m+ŸV%ˆœ$%†{ݤd²¤× &悉¹`š(¹L¤Dp½`b.¸˜ &[¹°!£¸Ád2—–λªy竟êI09ˆ” ]{WêI(¹Œ …˧êI tT¸%ÈÙéz�^\/˜˜ &æ‚i"äâ/@æí˜ϸ^01\Ì“­\X‚�QÜ�`2™Ë…ÆU‚l,]ª'ÁäŠ0R‚ôžÚ¡'¡n_.R%È×ù3õ$×»%ȵyz�^\/˜˜ &æ‚)Ósñ ¯^v‡Gô-™�ësÁÅl0ÙÊ…%Å&“¹ì¿ü™*AöUèI°öü×U 2ØP¥'¡.ÍQ%Èɽ ô$êÞvK�;yz�^\/˜˜ &æ‚)“s™¨ˆàzÁÄ\p1L¶ra BFq‚Éd.eï©DމjxÈyüÁ÷T 2Òß­‡¡myE• µg·êI œ›á– m¥z�^\/˜˜ &æ‚)Ss™ÈˆàzÁÄ\p1L¶ra BFq‚Éd.ïï]• Í zi¨¹Î=)êúWõ$ÒçkžU%Èû•zbÙÈ {BT)A[õ0½¸^01LÌS&æRÙØ9¡ Áõ‚‰¹àb6˜l儌ⓩ\ºúÛU"W‡‰¥ÿÂ7ªéüb™ž„êïiUȧK&;ÃÃzjYg¥[€œ~VÒ�ësÁÄ\0eZ.5ÍÝδwONèDp½`b.¸˜ &[¹°!£¸Ád*—šûªYwl¡žëÞŸãžõÄ= Õt³T• »=CORàþV·¹üŠ¤× &悉¹`ʤ\š:ú�§ß/WÈìMÕ¶\/˜˜ .fƒÉV.,AÈ(î@0™ÊåHí.U‚치¯'ÁÚ?}S• ×OéI¨ª’:£ �×U:�}Cú–‰‰ësÁÅl0ÙÊ…%Å&S¹l9ý‘*ANß:¨'Áø÷¤¨½zêÈö7T R}b½ž¤@å‹n ò°HÒ�ësÁÄ\0eB.Rx¼�w^ ÓsÎNøDp½`b.¸˜ &[¹°!£¸Ád*—µ‡þ\• �m×õ$ÒУ÷¤¨yª'‘vå<¯Jy[LÊ”NqK�¾;z�~\/˜˜ &æ‚ =)<äÈ)@äH9"$p½`b.¸˜ &[¹°!£¸Ád"—¾ÁU€,Úûcgx$ú¿¢õWrOŠºc±ž„èëTˆlòqJt׸HÙSz€�ësÁÄ\0¡ç2wûUU€ÈÉPï´öééÄÇõ‚‰¹àb6˜l儌Ⓣ\ê\T%HÞÑ_èI°îoóT Òs¬@OBy'E•£ARæÁ.·¹8S0p½`b.˜˜ &ä\Ö—4ªD.‡{é^—žf®L̳Ád+– dw ˜LärøêŽ„NŠÚñÛ¿pOŠzµDOB¥å¤¨7—º%È�Ez€�ësÁÄ\0¡æRz£Í™´ð¸*Av]x §ÙƒësÁÅl0ÙÊ…%Å&¹l*ÿ@• ·ÿ 'Á¯ùU‚ ·ßדP‡¶¼¢J�šSÁGŠXqée·‘#B€p½`b.˜˜ &Ä\ä¼òö)@–ï¯×ÓìÂõ‚‰¹àb6˜l儌Ⓣ\–½ªJ�–λzIŠ)@¤‰fû‡Ï¨äQÓ%=I�“Óܤ§N0p½`b.˜˜ &´\‡GF¯3+¿J}ž�¸^01\Ì“­\X‚�QÜ�`o.Í ªy竟êI°þ+GT ÒñÙ[zª·«E …˧êI ´¸ˆ\ × &悉¹`BËå­�µªyvõi§µgPO³× &æ‚‹Ù`²• K2Š;LãÍ¥ên™*AòO,Ó“`=GòU "'G ÒPS¬J�ý³ô$¹%Hå‹z€ƒësÁÄ\0!åòùùæ¬=j8®L̳Ád+– dw ˜Æ›K¢'EíܾP• ýUßêI(廊'÷.ГhÈqK�ë)üš âzÁÄ\01L(¹Ôµô¨òCJ�ÂÓMzš½¸^01\Ì“­\X‚�QÜ�`o.[N¤J�²›_ëI°ÖœYªj>÷Féo©äòÉOô$.¿â– ÷·ê®LÌsÁ„�Kïà°3=ç¬*@æl¹¢§Ù�ësÁÅl0ÙÊ…%Ŧñæ’{ä/U rëQ�žDénuOŠúÁ÷ô$Òî_ÏP%Hó­r=I�Šçݤ³Rpp½`b.˜˜ &„\üçéìÒÓìÆõ‚‰¹àb6˜l儌âÓxsY²o¶*Az:õ$ÒÀÕU‚´ç¿®'‘6ÿrŠ*Aú{ZõIJ¡ï¾_)@J&;ÎÞ ò¸^01LÌSºsñÎ2yQ‰SÙý¿QÙ†ësÁÅl0ÙÊ…%ŦñäÒÚÓ¢ �w¿™£'Ázޏ'Eýúoõ$TWÛU€l}šž¤@G…[‚œ›¡X¸^01LÌS:sñŸ䓲è—oÏF\/˜˜ .fƒÉV.,AÈ(î@0�'—GÝ÷U ²²øgzÌ;)jßù}zªãQ½*A¶øŒž¤@ãz·©{[°p½`b.˜˜ ¦tå"o{y~mÏ× &æ‚‹Ù`²• K2Š;LãÉÅ+AVxSO‚µ®�éžµ¥^OB¥¥¹6Ï-Aî¦ðD¬IàzÁÄ\01LéÊeîö«ª‘"„ç‰Äõ‚‰¹àb6˜l儌âÓxrIäH�áöûîIQ?ü�žDJK rvº[‚ÈÛbq½`b.˜˜ ¦tä"o}‘DÞ sé^—ž’× &æ‚‹Ù`²• K2Š;LãÉ%‘¤¿ú�*A:¶ý•žDJy "'B=>É݆{õ × &悉¹`Ju. Τ…ÇU "'E¥`\/˜˜ .fƒÉV.,AÈ(î@0�'—®þvU‚¼óÕOõ$R÷·yªé-Ù¬'‘z»ZT R¸|ªžX&—Ä•£@ähP\/˜˜ &æ‚)•¹´t 8Ϭ<¥ �·¿¬ÓS Âõ‚‰¹àb6˜l儌âÓxs‘D¶hä)Aä2¹±H "[JÜ+pK�«sõ× &悉¹`Je.³ò«T2#÷œ38<¢§„ësÁÅl0ÙÊ…%Ŧñæ¯iÍý‘*A†[c_¢0¥%ˆ\FJ�;yz€‡ësÁÄ\0¥*—ÅõªyjY™s§µOO)®L̳Ád+– dw ˜Æ›K¬d¤·Ã=)êªïëIt)-AÎ¿à– m¥z€‡ësÁÄ\0¥"—C׫ó€È&S|\/˜˜ .fƒÉV.,AÈ(î@0�7—X%È`C•*AÚó_דèRZ‚”NqK�¡N=ÀÃõ‚‰¹`b.˜lç"G}ÈÑrˆ B‰ázÁÄ\p1L¶ra BFq‚i<¹ô ö¨dáîêI¨ÞS;T Òµ{…žèëTHÁâIzbQ×%·©x^0q½`b.˜˜ &›¹Èy?äüR€Èù@(q\/˜˜ .fƒÉV.,AÈ(î@0�'—†Ç×T ²öПëI¨ž#ùª‘?cyÐP¡J�߯}NO,º¿Õ-A.¿¢˜¸^01LÌ“Í\ä 0R€ÈaäÊ0”8®L̳Ád+– dw ˜Æ“Ëé[U Rxjµž„J´¹v¦P•  gë‰E׸%HCŽ`âzÁÄ\01L¶rùü|³*@ä<  zJ‰âzÁÄ\p1L¶ra BFq‚i<¹ì«*P%È�šíz*Ѥ¼h‘*AN³TO,º8Ó-Aé&®LÌsÁd#—K÷ºœ)KJU ²¾¤QO)\/˜˜ .fƒÉV.,AÈ(î@0�'—‚²÷T Ru·LOB%Z‚ozY• ×+?׋NLuK��=ÀÄõ‚‰¹`b.˜LçÒÙ7ä<¿¶B s¶\ÑSJ× &æ‚‹Ù`²• K2Š;LãÉeeñÏT ÒÜÑ '¡-A¶øŒ*AÞ­ÔKzêܤì)=ÀÅõ‚‰¹`b.˜Lç"Ň Ï}tF"46\/˜˜ .fƒÉV.,AÈ(î@0�5—ž�NU€,Ùý<}gv©¤kïJ=‰ÔßÓª ¹2ÌÐ`¯žZò`—[‚\zYpq½`b.˜˜ &“¹È[_¤‘·ÂÈ[bhì¸^01\Ì“­\ÆU‚,_¾Üyíµ×Ô¶wï^= 5þüÑû”––êiè<77WOÇw ˜ÆšKuÓ)U‚l(Y¬'‘úÎïsK�—Èm¬=¤J�Ý¿ž¡'Õ/wK�›)8÷È8q½`b.˜˜ &S¹”ßjW'A•¤ðt“žÒXq½`b.¸˜ &[¹Œ¹‘ÒCJQ[[«ÊŒp………£‡ Þ}üs!syŽd°ÁĦ±æ²ÿògª‘“£F“H rîÐ*U‚œÜ»@O,’#@¤‘#BÀq½`b.˜˜ &¹ÈåoŸ~¿\ oíLîÿ)× &æ‚‹Ù`²•˘K)@üGÈçþ#=DøLŽþ�²C^‚$‹%&î@0�5—�¥KcžU$R‚ÈeqSvRT9ˆ” Ý5z€‹ësÁÄ\0�7—ÁágæÆ‹ª™žsÖéÖ·Ðxp½`b.¸˜ &[¹Œ«ñRj„¿%FfrÔ‡ÇÿùXÊ�±"X‚`âÓXs‘s�H ÒÚý*+‰” [ߟ¦J�ŽGõzbIß·)�¢ظ^01LÌÓxsYQ\¯ �©ïœpêY>gTázÁÄ\p1L¶r±Z‚xo“ñoÞcäc¹¿"Ï•,)Aä—Â�7;[ÙÅ?¨ä�}¯Þîmwvÿ�*A÷ËÀÛ«ÎWÈæeÿ8ðv“[ãÅ|U‚ôžœx;7nܸqÃÙò¸G€Èöñ·ïÃ�7nܲ{³ÁêÛaÂIá! “ðçJ�Ádë/*�ÏXr)¯/V%Ȧòô$XçÎ¥ª‘«Ä©¿´G• Å›Rpµ–[«Ü#AêÞÖl\/˜˜ &æ‚i¬¹Ôµô¨£?¤Y¾ßòQ‚YˆësÁÅl0ÙÊeÌ%ˆ¼ÍÅ;‚#Ú‰Qýü'C•Çùß&#åH¼%KLÜ�`K.;Îå©äHm쌶}ž£'‘¶¾?M• ­Í5zbQé·lÕƒÌÀõ‚‰¹`b.˜‚rÙZq_ r5˜–®=¥TâzÁÄ\p1L¶ra BFq‚)™\)Aä< R‚t}õ¡ž„êj»£ �ÂåSõÄ¢î·9ý¬d®LÌsÁžKkÏ 3íÝ“ª)(¿§§”j\/˜˜ .fƒÉV.,AÈ(î@0%“KcÛuU‚¬=ôçz©k÷ U‚ô�Ý­'¡ê/íQ%ˆ¼%ƺ»Üäò+z�9¸^01LÌSx.ÞÉP_È;¯'”\/˜˜ .fƒÉV.,AÈ(î@0%“Ë£îûªYYü3=‰4zRÔÆËzêÜ¡Uª9ýÍR=±èÆ"·¹½F2× &悉¹`òçRÿ¨×™¼¨„'CÀõ‚‰¹àb6˜l儌âS2¹$R‚¨“¢¾û¯£žõЖWT r½òs=±èâL·yX¤™ƒësÁÄ\0ùs™»ýª*@æl¹¢'”.\/˜˜ .fƒÉV.,AÈ(î@0%“K¼d¨¥^ ­¹?Ò“H»ržW%ÈûÁ—Ï5ªì)·hуÌÁõ‚‰¹`b.˜¼\*;U"—Å­kéQ3J®L̳Ád+– dw ˜’É%^ Òåˆ*A:¶ý•ž„t OR%ÈÐ`¯žZÒwÇ-@ÊŸÖƒÌÂõ‚‰¹`b.˜¼\^ÚpA• ö\WŸSzq½`b.¸˜ &[¹°!£¸Á”L.Þ‰Q?:8OOBõ–lV%H÷·yzJŽþ�ä÷kŸÓ‹Zö¸%Hu NÀj× &悉¹`’\ŠªªD.‰+W‡¡ôãzÁÄ\p1L¶ra BFq‚)™\.4–¨dSùzªçðǪé9’¯'¡î^?ªJ�¢�/é‰EõËÝDþÌ@\/˜˜ &æ‚éÊÕkγ«O«$çHƒžRºq½`b.¸˜ &[¹°!£¸Á”L.‡¯îP%Ⱦª= 5zyÜóûô$TJK�K/»%ˆ’�¸^01LÌÓ_ï8§ )B‡Gô”Ò�ësÁÅl0ÙÊ…%ŦdrÙrú#U‚TÜþƒž„‚*Aä\ R‚ôÔéAfázÁÄ\01= u÷úQU‚m|IO,�+ÂH 2® #¸^01LÌ%5Z{�§–•©äóóÍzsÁÄ\01\Ì“­\X‚�QÜ�`J$—ÃWw¨äË ëI(– æq½`b.˜˜Kj,Øs] /®«Ô“ؘ &悉¹àb6˜l儌âS"¹žZ­J�’ëÁ%G¼¤±ö�*A¾ÎŸ©'°¡`.˜˜‹}5ÍÝΤ…ÇÕVÙØ©§±1LÌsÁÅl0ÙÊ…%ŦDrY}àMU‚4¶]דP<Ä<®LÌs±oV~•: dÞŽÄ×ÌsÁÄ\p1L¶ra BFq‚)^.]ýíªY²o¶3<2¤§¡X‚˜Çõ‚‰¹`b.v×§'ÜýÄ-AjßÒƒÌÆõ‚‰¹`b.öÈ%qŸ]}Z• yÇîèib˜ &悉¹àb6˜lå’P 2þ|çµ×^S[ii©ž†òn—­¶¶VÍü�óßVXX2“Ï“Åw ˜âåRT½Y• ÅW¶êI¤ž#ùª‘?ƒt<ªW%ÈöŸÑ š¾ÛWH ru®d6®LÌs±'çHƒ*@žûèŒ*D’Á\01L̳Ád+—¸%ˆ¹¹¹êc)@¤Ø'·{EÆÞ½{ï#å‡7—ûŽ¥øðc ‚‰;LñrÉ;ú U‚Ô=¸¨'‘X‚˜Çõ‚‰¹`b.vÈ[_ä-0R‚È[b’Å\01L̳Ád+—¸%ˆþ£?äsïHOøLŽîç/JX‚L\Ü�`Š—Ë;_ýT• =ѯÀÄ<®LÌs±CN‚*ÈË—ô$9ÌsÁÄ\p1L¶rI¨ñË—/�xKŒÌäOPQâ/F¤ñ¿&Ú[lba ‚‰;L±réè}¬ �eE¯êI0– æq½`b.˜˜‹yr\ï’¸u-=zš悉¹`b.¸˜ &[¹)Aäs©!›ÿ1Rzxo© íí3ñH "¿nܸ�o;zþU‚¬)þóÀÛ½íî¶¥ªiØó«ÀÛÏžøR• ÛVÿóÀÛMlÎý�*AZ+Þ¼�7nܸ%¾ý»¿;©Žù?¶œ ¼�7nܸqK÷fƒ‘·Ã„ /5Ÿ#œ”&Éâ‘ ˜lýE¥ñ‰•KÍý U‚ĺ2Œèøí_¨dàj‰ž„ºv¦P• ‡¶¼¢'È r$Hãz=Èl\/˜˜ &æbÖçç›U2íÝ“NkÏ ž&�¹`b.˜˜ .fƒÉV.qK9‚#Þ‰Qýüçþr¤‡=M¼Û£a ‚‰;L±r9}ë *A¶Ÿý•žkËûSU‚ µÔëI¨ò¢Eª9ýMì2e\ÎÍpK�LJô ³q½`b.˜˜‹9½ƒÃÎ3+O©¤ üžžŽ sÁÄ\01\Ì“­\â– "èm.Rtxå…Þíáo{ ?_ˆ�™ÿ9Ç‚%&î@0ÅÊ%‘d¤·C �W}_O"}�?S• 7.îÒ J§¸%H“d6®LÌs1gÕ�[ª™‘{NOÆŽ¹`b.˜˜ .fƒÉV. • ˆX‚`âS¬\ªî–©dSùzIÞ#%HÇgoéI¤ÂåSU ÒÕvGO ë­w �Sõ óq½`b.˜˜‹wZûœÉ‹JT Rz£MOÇŽ¹`b.˜˜ .fƒÉV.,AÈ(î@0ÅÊ¥îÁEU‚l(Y¬'‘º®S%H´+ÃU• ]mwôÄ Ú·Üäî'z0±p½`b.˜˜K°Š†U€È%qëZzô4u˜ &悉¹àb6˜l儌âSP.‰” =Ç T Ò½?GOBµ6רÄÊåqÏ BiÀ\01—`/ä�W%È¢}7ô$µ˜ &悉¹àb6˜l儌âSP.ךϫdcéR=‰ä�µ¿ú�ž„º|òU‚Ùþ†žÆ„Ò€¹`b.‘¶VÜWÈ´wO:­=ƒzšZÌsÁÄ\p1L¶ra BFq‚)(—ºU ë¹�?ü�*A†;êI¨£;檤ª$OO c BiÀ\01—PrIܧß/W%HAù==M=悉¹`b.¸˜ &[¹°!£¸Á”K¼d¨éª*@Ú~ó=‰ôûµÏ©äAC…žÆ„Ò€¹`b.¡V׫dzÎÙ”^7sÁÄ\01\Ì“­\X‚�QÜ�` Ê%^ "o�‘¤sG”’d°W ²ÉÇV°¡4`.˜˜Ëõ�z�É‹JT r´®UOÓƒ¹`b.˜˜ .fƒÉV.,AÈ(î@0å¯é9’ïžõà:= õðn¥*@ähkX‚P0LÌå‰9[®¨DþL7悉¹`b.¸˜ &[¹°!£¸Á”Ëù;ÇT ²©ü= Õµ{…*AúÎîÖ“P×+?W%È�ÂÙzbÁ�En rk•L,\/˜˜ &æâ*½Ñ¦ 9DŽI7悉¹`b.¸˜ &[¹°!£¸Á”Ë�šíª)ªÞ¬'¡äm0R‚ôW}«'¡®�)T%ˆœÕšK/»%ȃ]z0±p½`b.˜˜‹£Îý1#÷œ*A–ï¯×Óôb.˜˜ &æ‚‹Ù`²• K2Š;LA¹l«X«J�Ó·êI¨ŽÍ?W%ÈÀÍà“ž¦¤9õŒ[‚t×èÁÄÂõ‚‰¹`b.Žº Œ rU˜Î¾!=M/悉¹`b.¸˜ &[¹°!£¸Á”ËG穤±íºž„J{ 2Ôé %“õ`âázÁÄ\0e{.­=ƒÎ´wOªdkÅ}=M?®LÌsÁÅl0ÙÊ…%Ŧð\:z«dÑÞ;Ã#Áÿš8úv˜êCzÊz ÒQá– g§ëÁÄÃõ‚‰¹`Êö\–ÝTÈ yçõ× &悉¹àb6˜l儌âSx.KT ’wôziôĨç÷éI(ïĨ‡¶¼¢'†Ý+pK�š7ô`âázÁÄ\0es.u-=Τ…ÇU R~«]O1p½`b.˜˜ .fƒÉV.,AÈ(î@0…粯ª@• {.æëI¤x%ÈÝëGU R´ñ%=1¬îm·¹“§× &æ‚)›sy¹à’*@æn¿ª'8¸^01L̳Ád+– dw ˜ÂsÙXºT• UwËô$RÚK�s3ܤ½\&®LÌS¶ærèÚcU€LYRê4uôë)®LÌsÁÅl0ÙÊ…%Ŧð\ÞùꧪéèÔ“Hi-A†{çø$w“�'(®LÌS6æ"—Ä}~m…*AV¸¥§X¸^01L̳Ád+– dw ˜ü¹tõ·«dYÑ«z,­%ˆý!G�ÈÑ × &æ‚)sY_Ò¨ �gVžrz‡õ × &悉¹àb6˜l儌â“?—ºãžUÄ+AkÙ+Aä< R‚ÈyA&0®LÌS¶åÒÒ5àL}ç„*Av]x §x¸^01L̳Ád+– dw ˜ü¹œ¾uP• Û*ÖêI°Žm¥J�þ+Gô$Ô哟¨ÄÊ%r¯ÌqK�û[õ`bâzÁÄ\0e[.óv\SÈÌ�õ× &悉¹àb6˜l儌â“?¯Ù~öWz¬-ïOU 2ÔR¯'¡Ê‹©¤òkôÄ Sϸ%Hw�LL\/˜˜ ¦lÊE.ƒ+ˆ\·¦¹[O1q½`b.˜˜ .fƒÉV.,AÈ(î@0ùs)¯/Ž[‚Œ ö«äÑŠŽ3<¤§¡Š7½¬J�úK{ôÄ�þ&·91U&.®LÌS¶ä"'C�‘{N• K‹nê).®LÌsÁÅl0ÙÊ…%Å&.‰ 2p³B• íù¯ëI¤ÂåSU ÒÕvGO iÙã– —^Öƒ‰‹ësÁ”-¹|RvW O¿_îtö—ÐH¸^01L̳Ád+– dw ˜ü¹$R‚ôÉW%H÷þ= õðn¥*@¶¼÷”žtc‘[‚ÜZ¥× &æ‚)rñŸ õóóÍzŠ�ësÁÄ\p1L¶ra BFq‚ÉŸKuÓ)U‚äŸX¦'‘:~û1OŠZ}b½*AÎÖƒ.¼ä– �éÁÄÅõ‚‰¹`ʆ\칞'CõãzÁÄ\01\Ì“­\ÆU‚,_¾Üyíµ×Ô¶wï^= 5þüÑû”––ª™ÿqá·%Š%&î@0ùsñ.‘»¡d±žDz¼êûªn¿¯'¡Žïœg廊eO¹%È@‹L\\/˜˜ ¦‰žK]K�:ª” •��zŠ�ësÁÄ\p1L¶rs "¥‡”¢¶¶Vá �ÜÜ\õ±”A÷Ñæ±°ÁÄ&.ñJ�¡¦«ªiÍ™¥'‘¾È�®J�æ[åzbÈ`«[€H’¸^01L=—9[®¨dîö«z’¸^01L̳Ád+—1— R€ø�þ�ÏÃ�æŸÉQ!R˜øù‹’d°ÁÄ&.ñJ�þ ߨ¤sûB=‰T°x’*A†‡õÄ�ör·‘·Äd®LÌÓDÎ¥¢¡C “•8õ�zõ43p½`b.˜˜ .fƒÉV.ã*Aü‡áo‰‘™”ž ¢$¨IKLÜ�`òç¯é>¸Î=)êwim®QÈçkžÕƒî~â– uoëÁÄÆõ‚‰¹`šÈ¹¼´á‚*Aí»¡'™ƒësÁÄ\p1L¶r±Z‚xo“ñoþÇøßR“,)Aä—Â�·ä¶o+v©$÷À‚ÀÛ›þþÿP%È­…�·—øX• »~ýÿ¼}<ÛãŠ?S%HóùwoçÆ�·‰¶m?vI ÿhI‰SQUxnܸqãÆ-[7¬¾&œõáþÉà‘ ˜lýE¥ññçrøêU‚치¯'¡:6ÿ\• 7+ô$Ôµ3…ª9ºc®žtõ»ç”#AšžA6‘q½`b.˜&j.¯^V%ÈŠâz=É,\/˜˜ &æ‚‹Ù`²•˘Ky›‹wG´£ú…ŸûC “ðR$,A0q‚ÉŸË–Ó©¤äú>= Å$u¸^01L1—¦Ž~uEÙäãLÄõ‚‰¹`b.¸˜ &[¹Œ¹A—¿•#;¼rCŠïö𷽄Ÿ/$Y,A0q‚ÉŸËGç©äÆÃj= •Ö¤æ – ”vÌÓDÌEŽþ�£@äÊ0™ŠësÁÄ\p1L¶rW ’N,A0q‚ÉË¥g S wÿÐ þWG ’:\/˜˜ ¦‰–Ëàðˆóôûåª)½Ñ¦§™‡ësÁÄ\p1L¶ra BFq‚ÉË¥æ~…*AòŽþB}d´¹~JOBÕœ*P%Èñ�óôÄ ¯¹W × &æ‚i¢å²ëÂU€LÏ9«'™‰ësÁÄ\p1L¶ra BFq‚ÉË¥øÊÖ˜'E]»W¨¤ï|ð9C¬ R¿Â-AäÏ,Àõ‚‰¹`šh¹ÌÞT­J�OÊîêIfâzÁÄ\01\Ì“­\X‚�QÜ�`òrÙXºT• UwËÔçAX‚¤× &æ‚i"åÒÒ5àL^T¢Nˆ*g2®LÌsÁÅl0ÙÊ…%Å&/—%ûf«DÎ  K�ÔázÁÄ\0M¤\ Êï©£@fåWéIæâzÁÄ\01\Ì“­\X‚�QÜ�`’\Z{ZTòî7sô4K�ÔázÁÄ\0M¤\^\W©J�­÷õ$sq½`b.˜˜ .fƒÉV.,AÈ(î@0I.ÞIQ×[¨§Á J^†Òˆ¹`š(¹ÜiíSˆ¼¦³oHO3× &悉¹àb6˜l儌â“ärúÖAU‚l?û+= ÖñÙ[ª¸Z¢'¡jŠU ²¿`–žä]æþV=˜Ø¸^01L%—Un©ä•ÂËz’Ù¸^01L̳Ád+– dw ˜$—DK�ÖœYªzÔ '¡î^?ªJ�¢�/é‰Açf¸%H{¹Ll\/˜˜ ¦‰’Ës�Q%HQõC=Él\/˜˜ &æ‚‹Ù`²• K2Š;L’Ky}qÜd¤¿[ �?øžžD²Z‚”NqK��=˜Ø¸^01L!—ÊÆNU€<µ¬ÌÑÓÌÆõ‚‰¹`b.¸˜ &[¹°!£¸Á$¹_ÙªJù3š�›ªiÿûŸéI$k%Hg¥[€œœ¦× &æ‚i"ä²`ÏuU‚ÈŸ× &悉¹àb6˜l儌â“ä’H Òs$_• Ýûsô$’µä^�[‚TÏÖƒ‰�ësÁ”é¹È‘ÓÞ=©J9"d¢àzÁÄ\01\Ì“­\X‚�QÜ�`’\)A¼“¢ö_9¢'‘¬• ׿¹%È�<=˜ø¸^01L™ž‹œD �ç×VèÉÄÀõ‚‰¹`b.¸˜ &[¹°!£¸Á$¹$R‚<þðªîŒ~Â>k%È™ç²ê¤¨‚ësÁ”é¹¼\pI• ëKõdbàzÁÄ\01\Ì“­\X‚�QÜ�`’\â• r5)@äê0±X)A[ÝDNŒ:2¨‡× &æ‚)“s¹ÓÚçLZxÜ™¼¨Äií™Xû8®LÌsÁÅl0ÙÊ…%Å&É¥ìæ×ªÙy~�ž†ê¯>¤J�΋õ$˜•äQ±[‚\0|t 8®LÌS&ç²´è¦: dîö«z2qp½`b.˜˜ .fƒÉV.,AÈ(î@0I.§oT%H´Käö+P%ˆœ5+%Hãz·©{[²× &æ‚)SsñŸµüV»žN\/˜˜ &æ‚‹Ù`²• K2Š;L’K¼¤sçR÷¤¨Õ‡ô$XCM±*A¾ÎŸ©'Hù‘e'E\/˜˜ ¦LÍ¥ðtÓ„È‹z)„Wbø_èË ¯”�Û¼û ùØ{žx�Í„D~†ðbÇ�%H|,AÈ(îÜ1I.e7¿ŽY‚tlû+·‰ru9ˆ” G¶¿¡']™Ã„`0Lè¹ÌÝ~U"f®LÌsIŽ¿ñ“Yxá•A·ùÅz¬H¶‘ÇÉæŒÿ6™{%LÐ}åcoþ5„÷½y�ó.›<·ðžGÊù˜%Hl,AÈ(îÜ1I.ûª T røê= Õšû#U‚ ·ÞÕ“P—O~¢JãW†çf¸%H{¹d®LÌr.­=ƒêr¸R‚Ô4wëivàzÁÄ\01—äy/öý¤`ð^üûIðÁÞæ‰õX)’)Aüß—<Þ+Âo‹v_™‡­pA߯|ŸòùÞä~Â+IX‚ÄÆ„ŒâΓ䲩üU‚\h,ÑÓ'Fú»UòhÅ =‰töÀ U‚ÈŸÆ�˜ê– -z�¸^01LȹxW„™•_¥'ÙƒësÁ”©¹tn_èþb 6y{v8yA//ì½""ÑDî/�Kô±É– ^ñ ü÷ ¿-Ú}ƒ¾ù>¼ïY¾¹]>ö—á%HøóðH�øX‚�Qü�.&ÉeeñÏT ÒÜÑ §OÈ[`ä?:8OOBõþX• =Gòõ$XCM±*AöÌÒrܤö-=È.\/˜˜ &´\Z{�)KJU "'GÍV\/˜˜ &æB”[k†%Å�;žówŽ©$êIQw,V%ˆ‹•s‚Hù‘¥'E\/˜˜ &´\òŽÝQÈË—ô$;q½`b.˜˜ Qrl­– dwîxНlU%ÈþËŸéI(¹»” 7cŸ˜ôâÑU‚œØý¶žpá%·y|H² × &æ‚ )—ξ!gÚ»'U r´®UO³× &悉¹%ÇÖša BFqçŽÇ+AäÏ ‰– ¥_¼¥J�êëõįiÍÎõÌõ‚‰¹`BÊeù~÷²¸/m¸ 'Ù‹ësÁÄ\ˆ’ckͰ!£¸sǯiÿûŸ©d°¡JO‚É Q¥‘¤ÃDDH˜ &”\êõ:“•¨D®“í¸^01LÌ…(9¶Ö K2Š;w<ñJ�ÖÜ©d¸õ®žDt6ÿrŠ*Aú{ úÍDDH˜ &”\fåW©dΖ+z’ݸ^01LÌ…(9¶Ö K2Š;w<&J�æ[åªù"wºžÂDDH˜ &„\v]x  ¹*Ì�Ö>=Ín\/˜˜ &æB”[k†%Å�;žcu»U òå…�õ$T"%HUIž*Aä¼ FUÏvK�»ô »p½`b.˜Ò�‹œ õé÷ËU ’s¤AO‰ësÁÄ\ˆ’ckÍ$T‚ÌŸ?ßyíµ×ÔVZZª§¡¼Ûe«­­ÕSÇÙ»wïè\žG†Ü_>OKLܹã9}ë *A¶Ÿý•ž„J¤9P8[• r™\£®ÎuK�&ÃÏ›!¸^01LéÎeiÑMU€<¿¶ÂÑSâzÁÄ\01¢äØZ3qK)(rssÕÇR€xE†ŸÜîRzx÷‘ûKÉNî;–âÃ�%&îÜñ˜(A¶¾?M• �š.é‰!,AôG„„¹`Jg.—îu9“çÉPp½`b.˜˜ Qrl­™¸%ˆþ£?äsÿ‘"|æRŽH)Ž%ÈÄÅ�;žx%Hû§oªdðV¥ž„êîhRˆœÕ8– ú#BÂ\0¥3—Ù›ªUòÖÎÐÿÿ!®TÌs!JŽ­5“P â/8–/_ñ–™ùËï12÷¿íÅ;¢D ÿ<Ú[lba ‚‰;w<ñJ�ŽÍ?W%ÈÀÍ = ÕX{H• »=CO b ¢?"$ÌSºr©lì=jSG¿ž’‡ësÁÄ\ˆ’ckÍ)A¼·½ø7¯ñß×›ûùß>“ )Aä—Â�·ØÛ' T RptUàí÷7¼®J�›%{o?²û]U‚|UðjàíãÙ:NýU‚Ü»ðŸoçÆ�·to3sJ• oo;x;7nܸM¤ IøkLymÎûÇu�<Æ¿ ×©žðûŠ w,ÈëUïóѽ6Ï$AÃ7Œ¼&œWj„¿&ZHþ¿Œ‰â‘ ˜lýE¥±Ûs1_• Gjƒ¯ÀïH�³V¨Dþ4îìt÷H�Žà¯=Ñq½`b.˜Ò‘Ký£ÞÑ£@Z{õ”ü¸^01LÌ%9á%EøëR!3ÿkÎðÇ„î/MÂoh%ˆ¼îö^[‰öúz¢°µfâ– ¸ºü‚c… ä¾Þ_ùÓ{¬TvÈ_ªð¿|‰` ‚‰;w<e賓ºé”ž„Jk R:Å-A†:õ »p½`b.˜Ò‘Ë¢}7Ü£@¾¬Ó Çõ‚‰¹`b.É /)äu¨ÿãåvy­é/)Âþ¹�Ïå1A·Å*A¼#Iü�ñßæ=¯<>è¾ò±7ÿÂ{½ì=Îÿ¹lòÜÂ{ù}ÈÇòsLT¶ÖLÜDøùÞ_< Î Õÿ—Âû è‘p¼Û¼€üd – ˜¸sÇóþþ×U ò¨û¾ž„êܱX• ýÕ‡ô$”µ¤ç»R€œzF²× &æ‚)Õ¹ô;O-+S%ˆ\†‚q½`b.˜˜Kräµ£ÿ5£÷ÚÓ#¯;½bÀ{M^lÄ*:bÝæ'_ÃÿšU^ßz¯kÃo‹v_™‡­pþŸÇ#¯½å9<ò½y¯·½’Äû^&"[k&¡KLܹc‘âC �%{¢'‘ºv¯P%Hßù}zÊZ "'C•äÊ=È>\/˜˜ ¦TçRxºI /ä�× Âõ‚‰¹`ÊØ\ªg»ÿÏ–ŠMN𝅗òâßÿîþrÀ+Âþ¹K âÿºþû„ßí¾Þ÷çç?X@ ¹=¼Ô/AŸG¾ÿý'[k滿i™‰%&þG‹we˜_ük=‰”¶Ä»2Ì�<=È>\/˜˜ ¦Tç2#÷œ*A>?߬'„ësÁ”±¹¤²¹ð’þ¢±�ê��½ÁÛ¼B Úc<ò¹ A·ù O´bC„ßí¾A%H4ò=yßK;k滿i™‰%&þGË—>V%ȶÒ_ëI¤x%HÍ©U‚Ýñ¤™7¢ây÷?v�•z�}¸^01L©Ì¥¦¹[ òvy[ EÇõ‚‰¹`b.É /)¤ð † RÁ;ŠÂÿ˜ðÏå1r?�÷Oøç"ü1RJx÷‘Û¼ïID»¯!þï#ï9b½Fž7èû�Hl­– dwîXr�ü¥*Aþpî+=‰¯¹v¦Ð| "'B•¤d²ãŒdï¸^01L©ÌeùþzU‚ÌÛÁ¿ ñp½`b.˜˜Kr¼ùÞæ/äs)ü¤døÍo~Q‚D{!Ïá¿=¼X2“òÁ»OxéôyÐ}åk{sy¾pò}ÝîͽïÍ{™Ëó³IK2Š;wÃ#C΢½?vîþ¡S]S¥§‘FOŒZõ­ž„²R‚´}·³–¤òE=ÈN\/˜˜ ¦TæòìêÓª9Zת' × &悉¹L|á¥��­5ÄŒâÎǽözuÈGçÅÌ%Þ%r­” �ëݤîm=ÈN\/˜˜ ¦TåRz£M O¿_®' × &悉¹L|,A̲µfX‚�Qܹ㨺[¦J�‚²÷bæ’–äú·‘2$‹q½`b.˜R•Ë‚=×U ²hß =¡X¸^01LÌ…(9¶Ö K2Š;w‡¯îP%Ⱦª‚˜¹´ç¿®J�Á[Á'(­üÃU‚”-Òªf¹%ÈãCz��¸^01L©ÈEN‚*'C•¤²±SO)®LÌs!JŽ­5ÄŒâÎGñ•­ª‘?cåòøÃ¨d¤;ø½ïÇwÎS%È哟è‰rù3)AZ³{s½`b.˜R‘Ë® T2=笞P<\/˜˜ &æB”[k†%Å�;ŽDJ)>¤y¼æOô$Òžu/¨¤ù–Á÷dzQ¸^01L©ÈeV~•*AÖ—4ê ÅÃõ‚‰¹`b.DɱµfX‚�QܹãH¤¨-S%HÇgoéI¨ááAgó/§¨¤¿ÇàUX‚(\/˜˜ &Û¹ÜiíSÈäE%NkOö^º;Y\/˜˜ &æB”[k†%Å�;ŽDJ�îƒëT Òs$_OBÉÑR€ü~íszbK…ësÁd;—Åõª™³åŠžP"¸^01LÌ…(9¶Ö K2Š;wû/·iÿôMU‚È!A.ÍQ%È‘íoè‰!Þ‰QéAvâzÁÄ\0ÙÎE.‰+%ÈÑ:ƒG½e®LÌs!JŽ­5ÄŒâÎG¼#AFû�Ç|Ï=)jo‡ž†:´åó'EWçº%HS¡d'®LÌ“Í\Šk©䙕§ô„Åõ‚‰¹`b.DɱµfX‚�QܹãˆW‚ 6T©¤mý«zéó5ϪäáÝàËçŽK…ësÁd3y Œ” kßÖJ× &悉¹%ÇÖša BFqçŽ#^ Ò{òwªéüb™ž„’¡JR¸|ªžÄDázÁÄ\0ÙÊ¥¥k@� uÒÂãê䨔®LÌs!JŽ­5ÄŒâÎG¼¤ë«U Ò{b‹ž„òNŠºû×3ôÄ ¯y°K²× &æ‚ÉV.yÇ@^.¸¤'” ®LÌs!JŽ­5ÄŒâÎG¼¤cóÏÝ“¢^-Ñ“P×ΪäðÖ9zbPÍn «Ãè� sÁd+—ç×V¨dOU‹žP2¸^01LÌ…(9¶Ö K2Š;w§oT%Èö³¿ Ì¥5÷Gªn½«'¡ÎX¡JùÓ¸s3ܤÓð¹F2 × &æ‚ÉF.•��ªyjY™38<¢§” ®LÌsIÞk¯½6ºùÕÖֆܶ|ùr}‹kþüù£·ÉÇ~±[ZZñ\………j‹eïÞ½Nnn®þ,6y.ÿ}åëÉ÷ïñѾFøóM$¶Ö K2Š;wÐ%Hé·êÔƒìÄõ‚‰¹`²‘ËÒ¢›ªyûË:=¡dq½`b.˜˜Krä…½W>H9!%€ðJ ™y¤HðŠ¹Í»¯��½ç‰÷ØDKy¹’L Î{žðç 7ž¯‘©l­– dwî8`K�žï^lHRö”d/®LÌ“�\ž~¿\• ¥7Úô„’Åõ‚‰¹`b.Éñ— ~2 /¼r#è6¿X�‰” Þý½MÊ ¯ ðfþ¯á¿MžÛ_fx÷ßä>ῆ÷}úŸO>–ïÕ»�îÍd“ŸÁ›û¿7ÿmòg¬R&l­– dwî8`K�û[ݤj–d/®LÌ“é\¤ø�䙕§ô„Æ‚ësÁÄ\’ç½h÷“íòâ=œ¼`ÿàƒoóÄz¬¼èÏ‘ þÁÿ±w›G>÷Ê ÿó„?g¸ðç‘ûÊ÷ë¾X߇'èþÿïÇ¿t±µfX‚�Qܹã€-A®/pK�[«ô {q½`b.˜Lç2oÇ5U‚,ß_¯'4\/˜˜ ¦LÍeSùêÿ)S±Éÿ·†“úòBÝ+"ü/Ôý¤ð— þ#"y¬”¦Þ#Ï!Ï%Âoóîžðç”�½ï_n ïûòÏc}ÞsÉíþþŸßÿØt±µfX‚�Qü�.ޏ%Hά˜%HEñr;%ˆwRÔ,¿2ŒàzÁÄ\0™Ì¥wpØ™úÎ U‚\º×¥§4\/˜˜ ¦LÍ%•%Ȇ’Åú«†’ã^A«”�¹ÿE½ðß?Öc…¿ðÄ{ŒˆU>„ßæÿÜÿ<áÏ.üy¼ïË?�ö}ÈÌûbÝ_È÷ï{I[k†%Åÿèâ€<DN„z|’ã”Lvœá^=Ì^\/˜˜ &“¹žnRÈ‹ë²û U&p½`b.˜˜Krüe„¼X÷>—õÞѹÍ{‘/·Éý=^Y â=VÈí^�!Â?á%A´òA„ßæÿÜÿ<ñŠyœ|/¹¯| ÿóEû>d&· ù8Úý…|þ;I[k†%Å�;ÈDŽþ�£@ο Ù�ësÁd2—¹çT òùùf=¡±âzÁÄ\01—äÈ‹wyÑïm~á·…¿�÷ß&›WHˆx�•2»W„“¢À»=Zù Âoó^|øŸ3œÌ¤˜ð¾/ï9üÏþµ¼ïÃÿ3É׋váý~ü¿³t±µfX‚�Qܹã€,Aîä¹%ˆœ„¸^@1L¦r©hèPÈ´wO:ƒÃ#zJcÅõ‚‰¹`b.” â‘’J¶Ö K2Š;w�%HÝÛn Ò¸^²× &æ‚ÉT.oí¬U%ÈŠbžÕ®LÌs¡L G† ¼FØZ3,AÈ(îÜq«Û­J�//|˜KÛúWU 2Ô\§'¡.ÍQ%HyÑ"=1àâL·yT¬Ù�ësÁd"—¦Ž~gò¢gÒÂãêc?®LÌs!JŽ­5ÄŒâÎÇÎóëT "eHP.›®J��›zêÚ™BU‚Ý1WO ¨|Ñ-Axe…ësÁd"ﲸs·_Õ/®LÌs!JŽ­5ÄŒâÎGÞÑ_¨¤æ~E`.i)AN=ã– ½< ]p½`b.˜Æ›Kec§:DީīS™Âõ‚‰¹`b.DɱµfX‚�Qܹcr–ì›­J�ŽÞÇ�¹°I?®LÌÓxsy!ï¼: dÁžëzB&p½`b.˜˜ Qrl­– dwîÛ®«deñÏÔçA¹¤¥©xÞ-Aº.éAvãzÁÄ\0�'—‚ò{ªyjY™ÓÚ3¨§d× &悉¹%ÇÖša BFq玡ìæ×ªÙrú#õyP.i)A.¼Äs‚øp½`b.˜ÆšKK×€3õ�ª‘2„ÌâzÁÄ\01¢äØZ3,AÈ(îÜ1Èeq¥‘2DåÂ$ý¸^01LcÍÅ;ê‹ë*õ„LâzÁÄ\01¢äØZ3,AÈ(îÜ1¬>ð¦*Aîµ»çÞÊ…%Húq½`b.˜Æ’Kù­vU€È Q/ÝëÒS2‰ësÁÄ\ˆ’ckͰ!£¸sO¿®þvU€¼óÕOõ$8– éÇõ‚‰¹`K.ÓsΪdiÑM=!Ó¸^01LÌ…(9¶Ö K2Š;÷ô»ñ°Z• ëŽ-Ô“à\X‚¤× &æ‚)Ù\v]x  �iïžtz‡õ”LãzÁÄ\01¢äØZ3,AÈ(îÜÓÏ;)êŽsyzœ K�ôãzÁÄ\0%“ËàðˆóìêÓª)<ݤ§d× &悉¹%ÇÖša BFqçž~ÅW¶ªDþôåÒ–÷§ªjqÏîì�ª9ýÍR=1àìt·i/׃ìÆõ‚‰¹`J&—=U-ª‘·ÃH!Böp½`b.˜˜ Qrl­– dwîé—P 2<¤ �G+f¨�ƒÚòŠ*AäˆcJ§¸%È`«d7®LÌS2¹ÌʯR%È'ewõ„lázÁÄ\01¢äØZ3,AÈ(îÜÓoÿåÏT r f»žDæ2Ô\§J�¶õ¯êI¤]9Ï«äACðÛe’Ößä '§éq½`b.˜Í¥®¥G S–”:�}Á%/™Ãõ‚‰¹`b.É)--u^{íµÑmùòåúWaa¡šûÕÖÖ†ïcùù¼ûøçÞL6ù¼¹ÿûóß&Æ*eâ±µfX‚�Qܹ§_"%ÈèIQ›®êI¨s‡V©ääÞzbÀ¹n Â+ÃŒâzÁÄ\0%’ËÖŠûªy!>�m\/˜˜ &æ’)ü/º½ãB>–ßBnþY¸ðç’ùAs¯ˆö\^IïvÿóF^ÈóÉý½ÁûØ›ûÉL¾‡ðçÿ<\øsÉ}å{õžODû>ü‚îïñÿnü÷ [kf\%ˆ×ôÈæý áäëÝG~Ážð¿pÉb ‚‰;÷ô‹W‚ w>TÈãUßדHû f©¤þÒ=§¡NÇ9>ÉÝäcR¸^01L‰äâ�µ üžž�m\/˜˜ ¦LÍeö¦jµoMÅ6wû“ “׋^)ÎÿBß{Ñíñ.Ús…Ïå9¼-è¹äëJ!ïvïye‹ö=…ÞÏà‘ÇÊó„Ï…7 ŽðÏåcïõ¶Üþ\RÉæŸGû>„ÿõ{´ûû§þÇŽ…­53æD~X_¨ü"Âù’Þ»�wùs¬X‚`âtÓ/^ Òåˆ*A:6ÿ\O".ŸªJ�Þ®='9úCŽ‘KäÒ(®LÌS¼\ZºœI �«M>¦ÔàzÁÄ\0ej.©,A^ÚpAÕØÅ…÷BÜÛä~Þ ú ±žË?÷>z.Áïvïyb½îõß_D+Âç›…?GøçáŸËû9üóh߇̼Ÿ9Öý…|ñ¾—DØZ3c.Aä—!?°Çûåø…Ï¼_FÐ_šd±ÁÄÿè¦_QõfU‚D»:LÏá�U Òýmžž„jm®QÈçkžÕrÜäÚ<= Áõ‚‰¹`Š—Ëú’Fõ?Ðò?ë”:\/˜˜ &æ’¯H'/¸ý¯C…W4ÈŸþןÞã£=Wø\ž[6™‡—r?ï5l"·{Ï+ßkP^„— òxyž ’Á›…?Gøçáäqò}{ä¾á_#Ú÷!3¹MÈÇÑî/äsÿïc¬l­™q• òËðø)™ùpÿ/P~ùÞ&ód±ÁÄ�{ú_ÙªJùÓãÏ¥ó‹eª‘+Ĺ}¹H• Å› ^ÅEÊ)Aî/ÙŠësÁ/—×UªDÎ B©Ãõ‚‰¹`b.É‘×�A¯ÃË!÷“×¢òÿëLïõjø\¶ ¹¿@¿-ü…~¬Ûå6ÿ÷þºØ#÷‘ÇÊ÷^&Èmò–­÷ätU‚Ü©* ¼�7nÜÆ³9{E ÿpñq§êÊÕÀûpãÆ�·ø‘ ñŽHñý ßl°úv˜pÞ/BÚ'T ÄÃ#A0Ùú‹J‰‹w$Hkî�T 2ÜzWOB�=°B• ò§1§žq�é­×\/˜˜ ¦X¹,ß_¯Jÿ õ(5¸^01LÌ…²…¼¾:ò%Y¶Ö̘Kù¡¤øÞ±Èý½Ãd¤ðð+ä±òÉ` ‚‰;÷ôc ’9¸^01LÑrqž~¿\• GëZõ”R…ësÁÄ\ˆ’ckÍŒ¹rd‡ÿm.B ÿÞíþÒCÈçÞmÉ"X‚`âÎ=ýŽÕíV%Èž‹ùzš‹W‚ŒôvèI¨“{¨¤ªÄàù;X‚âzÁÄ\0EË¥¨ú¡*@žû茞P*q½`b.˜˜ Qrl­™q• éÄwîé'—Æ•D.•ëñçòxÕ÷U ÍÑsU ríÌøaÅ$× &æ‚)Z.Þ¥×¾­'”J\/˜˜ &æB”[k†%Å�{úÅ+A¤a ‚�ësÁ”KSG¿3iáqµÉÇ”z\/˜˜ &æB”[k†%Å�{úA– åO³ Àõ‚‰¹` ÊEŽþ�£@ähJ®LÌs!JŽ­5ÄŒâÎ=ý.A†‡ô$�I®LÌSP.Ï®>­J9/¥× &悉¹%ÇÖša BFqçž~e7¿V%ÈŽsONlêÏ%ÞÕaŽïœ§J�Ë'?ÑX‚âzÁÄ\0…ç"W‚‘D® #Wˆ¡ôàzÁÄ\01¢äØZ3,AÈ(îÜÓ�—ÈÍ\/˜˜ ¦ð\ÞØV£J�¥E7õ„Ò�ësÁÄ\ˆ’ckͰ!£¸sO?– ™ƒësÁ²ët&/*Q%Hý£^=¥tàzÁÄ\01¢äØZ3,AÈ(îÜÓ¯¨zs̤íã9ªjºª'¡¬” g§»%Hg¥�àzÁÄ\0ùsY_Ò¨ �—6\ÐJ®LÌs!JŽ­5ÄŒâÎ=ý O­V%Èù;Çô$4—ŽÍ?W%ÈÀÍ = e¥¹ð’[‚´rÝúq½`b.˜ü¹ÌÈ=§J�­÷õ„Ò…ësÁÄ\ˆ’ckͰ!£¸sO¿œÃóU ÒØv]OBsa ‚ƒësÁäåRÙØ© �©ïœpz‡ÕŒÒ‡ësÁÄ\ˆ’ckͰ!£¸sO¯¾Ágáîªmp¨_OCs‰W‚Ôœ*P%ˆ\%Æ– �¸^01L^. ö\W%È[;kÕç”^\/˜˜ &æ’¼×^{mtó«­­ ¹mùòåú×üùóGo“�ýb=¶´´4â¹ Õ†N¾oùþ'[k†%Å�{z]k>¯ŽÉ;ú =qùs‰W‚\;S¨J�£;æê‰,Aq½`b.˜$9òã©eeª)¿Õ®o¡tâzÁÄ\01—ä俿ޖòâ~ïÞ½êc¯Äð¿à—@î/ä6ï¾B>öž'Þc‘KùÞà ?– ‰c BFqçž^Þ•aö\Ì×—?– 8¸^01L’Ë® TòüÚàý¥× &悉¹$Ç_‚øÉÌ+-<^¹t›_¬ÇŠDKï1²y÷—²EžÛ›yåKøý<ò¹w[¼ç÷.›WòxÏ!åˆ|Ì$1,AÈ(îÜÓkcéRU‚\h,Ñ—?– 8¸^01L’ˬü*U‚äiÐSJ7®LÌsIž÷¢ßOŠÿ‘)>øàƒÀÛ<±+EC¢%HÐóÈçþï5üsù^I!·…¿ ç—ïÏ$ˆ|Or?á•$,AÄŒâÎ=}†G†œ%ûf«¤µ§EO]þ\X‚ààzÁÄ\0?wÅ™´ð¸ÚZºô”Ò�ësÁ”©¹(œ­þß0[ÐÿÊ {y� ry\¢�M¦‘ç/dæ•"üsÿó}ò=xßï¦M›"ž?¼  ’8– dÿ£›>÷ÚëU²¬èU=yŸK¼äâÑõ¡“{è‰,Aq½`b.˜þz‡{YÜÙ›ªõ„p½`b.˜25—T– E¿ûÆòâÞ+‚J ¯ �¹¿xþûÇz¬H´ñÈ}½û�· â~– æ°!£øÝô‘·ÀH ’b™ž<áÏÅ+A†šëô$/‘›:\/˜˜ ¦¶â„*AŠªê !àzÁÄ\01—äx/þ…¼Ø÷>—úr”„”¹Í+ä69à/â=V„�þy8¹]„—ò¹w›�Ã{ùzþŸ/ï9ÂKyïëy?W¬ï3ÙZ3,AÈ(îÜÓçðÕ�'Eþ\¼d¸õ®ž„b ’:\/˜˜ ž£u­ª™öîIgpxDO × &悉¹$Ç{aïm~á·ùËá¿M69ï±R6øo÷*)0¼Û½%¨‘Ͻû…)4ŸÃôü›{ß“÷2—çg ’– dwîé³ýì¯T R^_¬'Oøsi];S• #Ý­zêÄî·U "o‹1¦üi·é >ú$[q½`b.xæn¿ªJ�¥E7õ„Pp½`b.˜˜Kv /E(y¶Ö K2Š;÷ôñJ�Ó·êÉ^.R|HòøÃ¨Ïƒì/˜¥J�Û—‹ôdœ†:ݤd²�‡ësÁÒÙ7äLYRªJ�šæn=%\/˜˜ &æ’]X‚ŒŸ­5ÄŒâÎ=})A®ŸR%HÛÇsÔçA¶­|Z• �êõdœ:+Ýäìt= × &æ‚¥ üž*@¦¯9©'„„ësÁÄ\ˆ’ckͰ!£¸sOŸDJ�Þ[T Òµ;ø|R|HR¸|ªžp'Ï-A®¼äîÁõ‚‰¹`y!ï¼*AVï­ÔBÂõ‚‰¹`b.DɱµfX‚�Qܹ§O"%Hçö…ªé;»[}îzå窑K¡seŽ[‚Ü+Ðòp½`b.8äí/R€ÈÛa*«¯ê)!ázÁÄ\01¢äØZ3,AÈ(îÜÓ'‘Ä;)êPKð[]J¿xK• •X£'œœæ– Ý5z@®L̇œUJ91*sÁÄ\01LÌ…(9¶Ö K2Š;÷ô‰W‚È%q¥‘"$š/r§«äîuCë«·Þ-@N|{ÍÂõ‚‰¹`�KáÊ%q¥‘Kä2LÌsÁÄ\ˆ’ckͰ!£¸sOŸx%Hßù}ª‘·ÄéïiUˆl}�z:NM…n R5KÈ�ësÁPTýP Ï®>­>g.˜˜ &悉¹%ÇÖša BFqçž>ñJ�îoóT "'G Ò|«\ »rž×n.uKù“"p½`b.foªV%Ț÷ÕçÌsÁÄ\01¢äØZ3,AÈ(îÜÓ'^ Ò±í¯T Ò刞†ª9U J�C[^Ñ.÷\R‚4®äÇõ‚‰¹¤_K×€3iáqµÝiíS3悉¹`b.˜˜ .fƒÉV.,AÈ(î@Ò'^ Òö›Ÿ¸'Em®ÓÓPåE‹T rîÐ*=1àÜ ·éä¥-ƒp½`b.é—s¤A2+¿JO˜ *悉¹`b.¸˜ &[¹°!£¸IŸx%Hkî�T "'H rtÇ\U‚\;S¨'œzÆ-Aä©�ësI¿ç×V¨d×…zÂ\P1LÌsÁÅl0ÙÊ…%ÅHú°É<\/˜˜Kz•ßjWÈSËÊœÞÁa=e.¨˜ &悉¹àb6˜l儌â$}â• �?øž*AFú»õ4”Õ¤ïŽ�× &æ’^óv\S%È¢}7ôÄÅ\01LÌsÁÅl0ÙÊ…%ÅHúÄ+A¤‘- ’z\/˜˜Kútö 9S–”ªäÒ½.=u1LÌsÁÄ\p1L¶ra BFq’>,A2× &æ’>å÷TòBÞy=y‚¹`b.˜˜ &æ‚‹Ù`²• K2Š;�ô�,A*_tK�V®× \/˜˜KúHù!%ˆ”!ᘠ&悉¹`b.¸˜ &[¹°!£¸IÈäÂK,AbàzÁÄ\ÒCÞþ"ˆ¼FÞ޹`b.˜˜ &æ‚‹Ù`²• K2Š;�ô‰{bÔUßW%HJOŒÊ$&®LÌ%=äD¨R‚ȉQƒ0LÌsÁÄ\p1L¶ra BFq’>ñJ�´\"—%HL\/˜˜KêÉ¥på’¸R‚È%rƒ0LÌsÁÄ\p1L¶ra BFq’>Ð%OŒˆësI½]¨äùµz‰¹`b.˜˜ &æ‚‹Ù`²• K2Š;�ôÙWU J�#µ»ôä É%^ R^´H• �æè‰ÞÕaúîèùq½`b.©7+¿J• 9Gô$sÁÄ\01L̳Ád+– dw éS|e«*AäÏp’K¼äì�ª‘?�êt �’Éz@á¸^01—ÔºÓÚçLZx\m-]z‰¹`b.˜˜ &æ‚‹Ù`²• K2Š;�ô�+A:*ÜäÌsz@á¸^01—ÔZuà–: ä•ÂËzŒ¹`b.˜˜ &æ‚‹Ù`²• K2Š;�ô�+A×»%Hõl= p\/˜˜Kj=»ú´*AŠªêI0悉¹`b.˜˜ .fƒÉV.,AÈ(î@Ò®¹:×-Aîäé…ãzÁÄ\Rçh]«*@¦½{ÒÑÓ`ÌsÁÄ\01\Ì“­\X‚�QÜ�¤\ "oƒ‘¤½\(× &æ’:s·_U%ÈÒ¢›zsÁÄ\01L̳Ád+—„J�ùóç;¯½öšÚJKKõ4”w»lµµµjVXX2—ÏcÍ“Áw éU‚ ¶ºHéÇÔC Çõ‚‰¹¤Fgß�3yQ‰*Ajš»õ4:悉¹`b.˜˜ .fƒÉV.qK)(rssÕÇR€H!Nn÷ŠŒ½{÷ŽÞGfAG´y2X‚`â$} J�–=n rq¦P®LÌ%5 Êï©äÅu•zsÁÄ\01L̳Ád+—¸%ˆþ£?äsïHOøLŽî,A²w éU‚Ô/wKù“¢âzÁÄ\Rã…¼óª‘2$ÌsÁÄ\01\Ì“­\*AüÇòåË#Þ#39Äã=FŠÿÛ^¼ÇE›'ƒ%&î@Òª¹üŠ[‚4®„ës±OÞþ"È”%¥êm1‰`.˜˜ &悉¹àb6˜låb¤‘Ïý¥†láG‹øß&ãmKLÜ�¤OQõæq• §¿Yj®©xÞ-A:*ô€‚p½`b.ö-ÚwC• rbÔD1LÌsÁÄ\p1L¶rI¨ñ—á¥H�h¥†”#A¢Íc‘D~)ܸqs·u‡–¨ä›3ŸÞþàoÿ½*A®Ÿ?xûî ÿA• ¥ûÿSàíÉl¥¤J�›WŽÞÎ�·ìݪ®\uþñR÷„¨¿/¹xnܸqãÆ�7Ùlˆ[‚ÈIOã�ÕÏ’T?9âCŽ" m�Ádë/*Å—sx¾*AGf ¹Ä;ä‹ÜéªyÐ`àè�SϸG‚ôÖëázÁÄ\ìÚZq_ ϯMn_Ã\01LÌsÁÅl0ÙÊ%n "‚Þæ"E‡W^HñáÝî&Bn÷?Ömž – ˜¸I�á‘!gɾ٪éìÑÓ'$—¶õ¯ªd¨¹NOŸt6ÿrŠ*Aú:õtNLuK��=  \/˜˜‹]Þ Q?) .d£a.˜˜ &悉¹àb6˜lå’P ‚ˆ%&î@ÒCŽþ�dõ�7õ$”äÒ±ù窸ù¯¯rô‡ Ÿ¯yVOÆI Ù(&®LÌÅžÊÆÎ¤Oˆêa.˜˜ &悉¹àb6˜l儌â$=ŽÕíV%È¶ŠµzJr‰U‚T•ä©äÈö7ôdœX‚$„ës±ç­�µªyûËÈ#Òâa.˜˜ &悉¹àb6˜l儌â$= O­V%ˆ”!A$—X%È¡-¯¨DÊ#X‚$„ës±£wpX"%È¥{]zš8悉¹`b.˜˜ .fƒÉV.,AÈ(î@ÒceñÏT ÒØv]OBI.±J�í>£J�‡w+õdœX‚$„ës±ãóóͪ™žsVO’Ã\01LÌsÁÅl0ÙÊ…%ÅHêuõ·«DNŒ*'H "¹´ú¦*Ao…½]-ª‘£Ê R�8>É-AF =ßÅõ‚‰¹Ø1{Sµ*AòŽÝÑ“ä0LÌsÁÄ\p1L¶ra BFq’zu.ªdݱ…zIr‰v‰Ü»×�ªd߆õÄ^"7!\/˜˜‹y-]Τ…ÇÕv§µOO“Ã\01LÌsÁÅl0ÙÊ…%ÅHê•\ß§J��ç×éI$É%Z R}b½*AJ¿xKO ` ’®LÌżõ%�ê(�™/êIò˜ &悉¹`b.¸˜ &[¹°!£¸I½//|¬J�#µ»ô$’äÒš3+°9±ûmU‚\<š£'°I× &æbÞK.¨dkÅ}=IsÁÄ\01L̳Ád+– dw ©·ýì¯T rúÖA=‰tíJµ*@­˜¡'OÝ1W• ×Îê‰,AÂõ‚‰¹˜58<2zU˜¦Ž~=MsÁÄ\01L̳Ád+– dw ©—H rãÌaU‚´­UOž` ’>\/˜˜‹Y•��ªyfå)=悉¹`b.˜˜ .fƒÉV.,AÈ(î@R/‘äÖþÿ¬J�΋õä +%Èùܤ½\(× &æbÖ'ewU òJáe=悉¹`b.˜˜ .fƒÉV.,AÈ(î@R/‘äî–¿V%HïÉßéÉVJ� /¹%H+×i,\/˜˜‹Y ö\W%ÈŠâñÆ\01LÌsÁÅl0ÙÊ…%ÅHêy%HÅí?èI¤9î•a#ÿ%Ö+AjÏnÕX‚$„ës1kV~•*Av]x 'cÃ\01LÌsÁÅl0ÙÊ…%ÅHêÅ;d¤»U �W}ßq†‡ôô  ’>\/˜˜‹YÏ®>­J�K÷ºôdl˜ &悉¹`b.¸˜ &[¹°!£¸I½x%Hÿ•#ªéøí_èI(– éÃõ‚‰¹˜#W†‘dÒÂãêãñ`.˜˜ &悉¹àb6˜l儌â$õâ• Ý×¹ç)Ù¬'¡¬” g§»%Hg¥P®LÌÅ9úCJ�ç×VèÉØ1LÌsÁÄ\p1L¶ra BFq’zñJ�Îí U "G„±R‚ð¹ ázÁÄ\Ìñ® 3wûU=;悉¹`b.˜˜ .fƒÉV.,AÈ(î@R/^ Òö›Ÿ¨d¨¹NOB±I®LÌÅœ9[®¨DÊ�ñb.˜˜ &悉¹àb6˜l儌â$õŠª7«äðÕzJ ÙFûõ$Ôéo–ª¤òkôÄ€SÝd E(× &æbFïà°3eI©*AêZzôtì˜ &悉¹`b.¸˜ &[¹°!£¸I½â+[U "ñJ�hÎX¡JùÓ)>¤){J(®LÌÅŒ=U-ª™žsVOƇ¹`b.˜˜ &æ‚‹Ù`²• K2Š;�Ôƒ+AÚJÝäü z@Ñp½`b.fxo…Yuà–žŒsÁÄ\01L̳Ád+– dw ©W‚Ü^ã– ×èEÃõ‚‰¹Œ_K×€3yQ‰º4ný£^=悉¹`b.˜˜ .fƒÉV.,AÈ(î@R®©ží– ÍŸëEÃõ‚‰¹Œ_ΑuÈË—ôdü˜ &悉¹`b.¸˜ &[¹°!£¸I½DJ�‡ïýýY$ã%ˆwRÔþ&= h¸^01—ñqžYyJ• Å5�ôôÿßÞýÄXY�qß°páÂ… ]¸èÂ4.LJÚ.LÊÂ…iMÓ¤¶•¤1,\š£QÛ±¦Ö?ñÏ„F,’�:1"±5…QlˆŽAœ)ЍdF«”¡Š£€ 0ü½ÍsîyäÌ;ï¹Ïyçî÷“¼‘yfXp~sÎ}ïϹs»G.6‘‹Mäb¹ØE66åÊ…Iq€TÏÔO‚œ|¯^€È[ä¢)ö‹MäÒý…¨W=ø¦Ÿ¤A.6‘‹Mäb¹ØE66åÊ…Iq€TÏT røÉz òÁ ?@#ì›È¥;×>>êJ�5Ó~’¹ØD.6‘‹MäbÙØ”+J$ÅR=S%ÈĪz "ešb¿ØD.�›˜:í �Kï©MŸ™õÓ4ÈÅ&r±‰\l"»ÈƦ\¹P‚ )�ê™*Aö.«— G‡ü�°_l"—ÎÉÛáJ ò› ûý$r±‰\l"›ÈÅ.²±)W.” HФz/¼»Þ• /ïßè's}yïÒz òuùÿ•ݹåvW‚¼=ô'?éÂŽËë%È™C~€FØ/6‘Kç–ö¿íJù½ ©‘‹Mäb¹ØD.v‘�M¹r¡AR Õ[¿ã®Ù;9ì'sëÿ©+A¾>vØOæÚú×\ òѾçý¤ R€È…–°_l"—ÎÈË_¤YrÛëÉ_ #ÈÅ&r±‰\l"»ÈƦ\¹P‚ )�êÝ·õfW‚LÿÐOæjV‚l|àJW‚|qxÔOº@ Òö‹MäÒ™¡G] rÍ£{ü$-r±‰\l"›ÈÅ.²±)W.” HФZ§ÏM»ä¶M?©�Ÿ=ë§s5*AΞ>æ �õw,©ÍžŸñÓ.P‚´…ýb¹tæ¡WºdÕ?&ü$-r±‰\l"›ÈÅ.²±)W.” HФZ޼ãJ�þm¿õ“ù• “ãC®Ùôç¥~Ò%J�¶°_l"—ÎÜôô˜+AR¿5®"›ÈÅ&r±‰\ì"›råB ‚¤8@ª%¿ UJ�ÍûÖùÉ|�Jùe¨R‚¼1x«Ÿt‰¤-ì›È¥3ò2)A^›8æ'i‘‹Mäb¹ØD.v‘�M¹r¡AR ÕjöKQE£$é/E;¿S/Axw˜–°_l"—Î\zçˆ+A¦Nžó“´ÈÅ&r±‰\l"»ÈƦ\¹P‚ )�jÝýâ/\ rb樟Ìרè»Ì• §N|ê']Úue½™ùØÐûÅ&riß§'κäòßïð“ôÈÅ&r±‰\l"»ÈƦ\¹P‚ )�êÈ»ÁHòà+·øI¹X "ï#Èß¾ÊO i ûÅ&riŸ¼FJ�eOìõ“ôÈÅ&r±‰\l"»ÈƦ\¹P‚ )�êÈK`¤Øu¿Ÿ”‹• ò)A†ž¾ÑO i ûÅ&riŸ¾3Ì­›Ëߪ;r±‰\l"›ÈÅ.²±)W.” HФ:[Çžq%È–÷Ÿò“r±dÏË÷ºd÷?ïò“(AÚÂ~±‰\Úwýºw] òüÞÏý$=r±‰\l"›ÈÅ.²±)W.” HФ:÷<æJ��oõ“rGÿøCW‚|3sÂOê^{n…+AöïZï' _R/AÎçywˆ‹ ûÅ&riÏô™ÙÚ%·»D~7H.äb¹ØD.6‘‹]dcS®\(A�Hu´ÙýÉ+~2ßìÔÇ®™z`™Ÿ\ %È�7ü¤K§ö× �7®ð4Ã~±‰\Ú3°ûSW€\ûø¨ŸäA.6‘‹Mäb¹ØE66åÊ…Iq€T§•äìØ6W‚|¶öf?¹ y 2µ¹^‚ì»ÎÐ ûÅ&riÏuk÷¹dÍð¤ŸäA.6‘‹Mäb¹ØE66åÊ…Iq€Tç‰á;\ ràÈ;~2ßÌðS®9¼aþïýزv™+A&LJü¤Kª— «üͰ_l"—Ö:v¦¶ä¶×Ý5uòœŸæA.6‘‹Mäb¹ØE66åÊ…Iq€TGK�‰Ï÷ùÉ|Ó¿Ë• _|ÜO.Ðäð‡‰öÒØMõdr� ö‹MäÒºG·ý×ýÈ ëßó“|ÈÅ&r±‰\l"»ÈƦ\¹P‚ )�ê´R‚ÿËM®ùÏðf?¹ y ²çšz rŒ½Ù*ö‹MäÒº«~Ë• ϼõ™ŸäC.6‘‹Mäb¹ØE66åÊ¥«¤¯¯¯¶|ùrw úé\+W®üökFFFÜL¾Vgrõ÷÷»y;(Alâ©N³䛳§j_Þ»Ô• ãïÍÉLÒdvºV{}I½á�aZÆ~±‰\Z³ó“¯\rÙÝÿªÍœÿÚOó!›ÈÅ&r±‰\ì"›råÒq "E†” b||Ü•EßR€è×Èßí¤øQ‚ØÄR�f%ÈùOF]r|Í/KsIZ‚© o]íhûÅ&riÍŠ�¸äö>ò“¼ÈÅ&r±‰\l"»ÈƦ\¹t\‚Hþô‡|¬?顊3ù©)L(A.^ ÕØu¿+AöNûÉ\3o<ëJ�“ƒ÷•æ2ôô�®ùhßó~Ò…C«ë%È�[ü­`¿ØD.ÍMŸ™­]zçˆ+A&¦Nûi^äb¹ØD.6‘‹]dcS®\º*A‚CJ�°2“ŸQúwäë—Ãÿ^+(Alâ©N³·È�~îW‚œÙ³©4—¤o‘ûïë%ÈÿÖûZÁ~±‰\š{rÇaW€\¿î]?É�\l"›ÈÅ&r±‹llÊ•KÖD_&^ÅŸ‘�eÞ.J›8@ªÓ¬9¾úg®™=2QšKÒd÷wë%ÈÉüïq1a¿ØD.Íýhõ;®Ùòþ~’¹ØD.6‘‹MäbÙØ”+—®J�°ô(–"eäå0eôe2íuEW¯^^^[ûÝæ_Õ^zuSéç®þuíÐc?/ýœ\/>{Oí©{®¨½òÒ†ÒÏ·sÝþýÚWÛ¿Wú9..®‹ïúñ#Ûk?xp{íÕmåŸçâââââââêöóÏþÓ긑—¹Hñ!b¿5þ’Ô�'±r •ŽK{û[-5¤øÐÏka"¤ ѹ\íþ@»º*A JÐ(A@O =�ôJÐ(A@O =aÑ• ƒƒƒµåË—»«¯¯ÏO±4½-Œ��·æããã~R«�ŒŒ|›ÅÊ•+ý4>GzºdÍ•ì ]ÿðs’]8G�Ö9œ‡{)6G:±\úûûçÌeO©pN.ùÄÖY?tžq±9Ò*[çðL.Ù?*|ì ÷Ò“‹ÝÃ! YëðŒ*î½WŽÍ‘‡d¢—�µ.;£Š{D¾FÏA¤;‹Â½ ûIϻؼ]‹ª p;èQ�²oZ2ZXrHëÁP|`•�%›Øù身âÇ¢ø€+—=0#­ðŒ X…æ›#Ÿ0ù¯|\D.Õ“,ôI]ñI�>þÄæÈ',žb÷]ò¸Ç!¤!ë,™è%½#ô\‹Í‘ž|¿‡ë.ôÌ*’LôkD˜%Ò*{ì–ŒÂç&šYlÞ‰EW‚„ÿPùfÕ’oZ½42ZXáÍfqíÛӲ9ò‘õ–uWòq¸DYú9ä#Yȹ%Šë­û)6G>a.ò˜¢{E.]{r©Ž®}øØQ\o=çbs¤'k-¹È•ìÍK.ÝGÅô Ò“uáçâzëçbs¤§g“¬·>W‘�Ãý¢óbáßAZáúëcLq½õ¹KlÞ J$!‹äCF +¼ù,®½Ìåó±9ò‘õ–u/#ûE®²äùÈyî…âzë~ŠÍ‘G1—�Ü”êçÈ¥záú×[ϹØùÈž‘u/’u×}RÌAwäï!­p]Ã'ÒÅõÖÏÅæH+\WYoY÷2²_dŸshôw�Žœc²ÖÅõ–Lä ‹Í;ÁËa�„d´°Â›O9›"=(bsä£ë^F×_r ŸÔÉÇe7µH£¸„¬øäMóˆÍ‘^Y.¡p_�ËÂÐu–ÂsM>Ö|ÊæÈ+¶Î:—ǹ'S�—Ð9YoÙ#á%÷Ár…Oªåc½G.›#-ù~/æ"³"]ÉDþ¬ôyòÒu– Â|t¯Äæ�XT%ˆâòM«‡¼,‚üã±ðô¦‡ŒVñ&H²Ð›9$ô�Í‘‡ìƒØÍf¸þ’Ÿî™uz°£1ÉBö@‘ä¤Y„´±9ÒŠå ÷¹T#\WYg9§„ä YHvÍæHKÖXïc{'\Ù+š¥Þ«!/YóðŒ ÷‚¬¿ä›#Ys=£Štý‹{J2’òÒuÖ3J÷‚œ]’[lÞ‰EU‚9Lä/Wìùéá —ò‚Œª'‡@˜‡®» :+ÞÈ–Í‘–ä ë®uÙL„û*¼)BZ²¶ar }p g"6GZ±\Ây¸/È¥áãEq�Ã¹Þ”ŠØéïÃt�‹�;áú‡{‰'tù…%ˆïÕÂ'n±9ò�5.»O.®¸—‘N'Ï'cóv-º ”  'P‚€ž@ z%è ”  'P‚€ž@ z%è ”  'P‚€ž@ z@­ö/±Ì’e ¢ïIEND®B`‚sogou-srpc-ac65014/docs/images/benchmark6.png000066400000000000000000001121151525217424400210720ustar00rootroot00000000000000‰PNG  IHDRBhb%DsRGB®ÎégAMA±� üa pHYsÃÃÇo¨d“âIDATx^íݘ•õ}çýìS7MZ»±‰mÌÆ6¶1Y»µ[»kÚ¤±YûÔ§ÉÕØ>vkvåJ./sm²ëCÓÖµ@QüŠƒJbtbP´e‰ø/¨ òOTjGù�€÷Ã÷wß¿Ãoî¹Ï™óo~ó™sÞ¯ë:—Ì9gÎ óžs“óÉ™3ïJÚCh ! m0„€¶ÁÚCh ! m0„€¶ÁÚCh ! m0„€¶ÁÚCh ! m0„d|ùË_NF�U:uttd—`¤¹æškúµ´¶½½½Ù¥Ã‡! ¡§§§ßg;1„Œ\ù!ÄNÖ`¸1„$„CHѲråÊ~ªÃÓèÑ£“ÎÎÎÂgÜ|óÍß§Òƒs»={ÿK/½´ô~öÌ{�ßÝÝ�]«2»žý}òïß.£@WWWéï^Íß9P¬9c_sû>·ïwÿu-úÞŸ5kVéòüɾÿíúåzTºoúÏ(aH°eþASÑ^^îd#CþÁv8B”;�v^þGuò§üÇÊ+zVDxjµ!a¯j®_-ßš¶ËÀRn O^Ø©ÒÉn3¯šû&C@ C@Bø`j°!$¼Ü`Ùÿ[í/³ÿ·;TôÀúµ×^ë÷>v ¨=ôÐCý.³‰ö>Æ®g#‰}œJÔí:þýíÿQ÷·oÿµÛ³äí@eªÛUŽ ö½Ž}6å¿ÃNög/ü~õ—Û}'d_OYÑ}5 ! ƒ=˜ªt¹�þ2;…£F¥Àá� Ø�xáùáƒÂZ„׆•v>À®f€¨Ô«Cu»Šìûßÿ]«ýû–B<OÂ1$¼Mû³??ß@C@Â`¦»Ü_f§ðAZ¥ÀáeþÁ_øl�FžµQtÛƒ±AÇ®1ög;Ï?#Åó?êaÿµ©þãÙ³OÂ×l°·ó¿cø¹ÙhûûüöþE?:Téã‡ìöýmå¿þE*õ Ùçšý;Ùç¾_¥×¾È÷µ÷ó/Êßž ¿¾ögûz†Ÿ‡ÿšÉ ëk_së~®E?‚~-Ëݾ Ÿ b«ám‡ß¡ð:áíÚßÉŸoýPÇ�0؃©J—矪ôÀ:¼Ì?ø d&ÿ#µŒúÛ Ÿ©’g‡üƒúðd—…cˆÿÜí�t8\ØÉ„çå4‡G?rØuò·žò_‹Á>¾>xÎý‹Têªô¹ÚÉ? §Ú!$üQ¦¢S8…ß‹åšÙmç{Wú\ìëk üÛáçæ…Y¥ï¥ðzE#V‘j†�rŸ_¥û&ŠB{0Uîr{@>Íÿ?éåXÛ¨>˜öœÃë—{@X�üíû“Ý~øyxá8áLÇn£è|~žù“)÷~&ü¼ü¸â<ÛeþóËmÃAe°�ï åb­ÃÏ)üXùgBTº]û{úËìïëG»žÿZÙ=;ß_ßNö±ì}ìk~½ÂïŸð}ò_c{ÿ}¾¿ÿž4áaשÄ_ÏNù¿k9á×®Ò÷}xÛ^øw³¯3êB{0^^îdþý{¯è°ðéÏ·À ¯_éa5ìã„·žìsõìzþü¢¿{ÑeáíÚcÿwóÃ@øã/áçð|?„φÈÿ�ï{xÙ`ß `ûëTÞn5×Ïóï›ÿ:VºÝp4Ê_VôùÛýyùQ"üú†ŸC¥a*>k$ü Ÿa4س<üõìTí×0ü{æ¿Bám{á×£è4Øp@l ! ჩüƒX3؃-{PŸALø¸èdÿï|¹g:Tz@X ûÜÃÂùÛìïæOå{ÿ"þÙ v²±Å„Ÿ‡ÆAø ¸Ò)|`^ÍÇ7ámWºžWííZk»íJ}C•n·Òm„'?@Øûûóì}Cå.«ôñCö÷ò׳“ÿžÜ¥èû<¾¥� ;ùïË"ám{áß¹èÄPÃ�PéÁ¥)ºÜÜÛƒ,¾†C(|žìýì_þAeøL‘ð�3ä?_ÿq°’þþýªy`ŽþYá�ÀxáƒàJ§r?šSîã›ð¶+]Ï«æv­Y8 ”;…*Ýn¹ï‘üÉföþþ<{ßP¹Ëªù{ya7_‹)úÏ °f¾FHøw G¹J_1„$ ö`ªÜåö 8|à—�YËPþhBø`¯Y¿‡�òçUû@²š¿WþÇcÂÔáÈS̓à¼j¿®C1„„?&bÿlãÏ·S¨ÒíVûwñì:þúö¾¡r—Õò1ò?^SË�ŘzƼj¾ò_w¯Ò×E ! ƒ=˜ªtyø-™½í/ì¨ _¯ÃN•^Ï¡áßÃ?#$(ª_ªý{…Ïœ û‹1å^—¢’j?þP !å®c£˜?ßN¡J·~]ªìýýõívCå. ?F5ßSa·ðσýXŒ±¿ƒ¿¾�Âý*g°!Än£ÜàXéë€"†€„ÁLUº¦} ì{Ê�$ádz?{v]{_» ¹ÿš{•¾(bHìÁÔ`—‡tÃŒáBÿà³vþýÊ�* !E×Oùµùÿǽè~þÕþ½ìvÃÛ°SÑêð�z¹S¨Ú�ßÈRt²¯yÑßɟ¯a(üq“ðä Ö;üž³¿GÑù¦Òe•>F~±"�p Œ Yùa¨èT4„T:åGSéï €"†€„ÁL…~óMøþö`Ø ÿ ¯Zv›öà5|¶†Ý¶Ýføÿ°©ô¾åÐÚƒßüûØŸí}ìöBµü½ÂÄö9äo˳óívÃ1ÁÞ×>'ûÜBÕ~üZ‡�ðv‹N~|òŸ«?ß>OûXþãÙ×-Ï. ¿¶ö÷ YSûÞó—ÛÉÞ¶÷óÏ�1•¾û>µ�ö°ÏÇÆ…ðö½ðs ¿§kaÏ>�°©}Lû»‡=ìëê/ÏŸìó°Ï1ÿ=à öw@ C@‚=( x¡5Ô:„à˜ðk—m@ýBBZCHýÂgq ö¬P=†€„pñ'‘‘+ü±b©^øš-E?æêÇ�þ?àvb¹òCˆµ-÷È~Æí*½(/¨Ch ! m0„€¶ÁÚCh ! m0„€¶ÁÚCh ! m0„€¶ÁÚCh ! m Û2zô褣£#{ `è Ë2jÔ¨¤»»›!D5lÏéééaQ1„€¶ÁÚÆˆBÖ¯_Ÿ<÷Üsœ8qâĉ'Nœ8qâĉ§8½ôÒKÙ#þ¡7"‡�Ç{,ûTØ7.ôÐE]4ÑE]ôÐD]4ÑE]4Åì}éííu¿5&<Ù(R †�ˆv-O’Ug%ɳçggêoÚ‡ö%Ïm_™,è��t-½&¹îá¯$·.¾"»å(äîëKvíÜœ¼òücÉægæ$Ï=Õ•<ýÈÕɲŸŽK»ç¢ä'3ÎI~ÔyF2ëÚS’ù·�›½WkáßÔ;÷&Gú^q§C[V%o¿¸°Û=kcÛ‹=ÉÏŸ�WzÖÆ’y—¹qà ÿ¬�¢£ÒéûcßãÞoÎ�Ÿt#‰ÝÞŠ‡&%ë}'yaÍl7¦ØÇ¶Ï¡UÅþÇ÷�½}n¸8¼sk:\=\¿ ôÌ ?^ì}`jé™6^ØéÍï|!0¦œS8B4rzãª?)�#ö ÷1ø÷¥ÏÁ^6°Øçiƒ‹}î6À¸g’¼õjö7lþG‘&ºè¡‰&ºh¢‹&ºhŠÙ…!Çìßœ$Ïœ› vzúÌ$Ù½*»°²j¿imÀX¶åኣ‡�¦<ò¿“;—OIßtòÂëë’C‡f·€Zø.GŽrãÂk[—'[7ÌwÃÆªŸ]_úq$l˜°�Â†Š¢£Ò©«ã„äîëOuÉ£3/HÝÿ57nØÇ±!Åûø‡íwŸO»ìþòΡƒÇ~ddëÚt¼è]\.ö/¼=.œ6p¸¸é‚t\˜ú¹Âñ¡'»m7^ÌøbúqïüÇôóèž|l¼xj¶û\®{¤4¾ø¿“ýýñ?Š4ÑEM4ÑE]4ÑESÌ. !°G]Iòó들çøt±ÿn�šž_¥¢oÚ»_IV¿¼0™»öV÷c,cçžW8z\óЗ“;–]W=ìõ@0¸=o¾”ìÜöLéÇQì6>ذñP×yn”¸ãÊ'·_~|áxQétë˜ãÜ(b?Òb¯Ùa·iϱñÄÆ S¶oYâÆ YÐÿõ.üÜxñÔìÒ@`cÁ/~ðÜx`#BøZö ‰¢á¡ÑÓ×~¦ô1ü`R/ì™�Þä>7{M?¶ø¿ÃámÓ¿W“Ÿ}¡ˆÿQ¤‰.zh¢‰.šè¢‰.šbvaiwo-I’å§{ȺóÒg†Ô詵‹J£ÇÍ=ã’ñ?þ»ÂÑcâO/p/rúÀ³?HÖm[šì9øVv °×ÙxkÇ&7l<¿êîäÙ'oqÃFÏ�.vϲ°aã_§œ–Üqʼn…ãÅ`'6ìÇYÂaÃ~Ü%ÿ¬�Vþ‘”4xzãêO/¾ÿe÷1í…BÝxþÈÈÑÏsËßu¯Ñᆋ-«J'{V †ÿ£H]ôÐD]4ÑE]4ÅìÂÒŽ^�™$‹O<öc0[®N’#ůݰïíÝ¥_[{Û’«Ü¸Q4ztüø¸Ë[}ôØ»k[éÇQlØÈÿv”F_gÃÞ׆ ûÑ»M6ìcØ�¾ØÇ´�m?S­a?È9\z�_ø¬ ÿZÃñ¬‹£'ûöºåžu±ïñ[�=ë¢wq:^l][ú{Ùß±Vü㫉.šè¢‡&šè¢‰.šè¢)f†�v²oS’¬9çØ³@Ö~®ß�ÁØ ’nÙ¹Á½V‡½P©½`iÑèa?öb?þbÏYûÊâ¤oߎ{0ñ/"j¯wþv”ü³6f^yRáxQé¾Î†ÿí(öâ¤þu6^î}ĽxéP¾ÎF=]J¿adÇæcϺXýÓt¸xò®Ò3ü`à=ª�ü°`ÏŽ(=Uû¬‹ýKïqŸïÁµ–þöSìïe¿áÆ?¾šè¢‰.zh¢‰.šè¢‰.šbvaiö¢§ö¬�…ïI{6È«3ÝopYþóŸ•~ƒ‹ýŠÚüèa/pj/t:gÍ w]{Ô"JûQÿZö#"þµ6ž¸÷+É#wœïÆ (êyQÿ:v6lØ`b·mÃÆægæ¸�9¯³ád¤ß¯G]û û òãEá³. †‡fœüí—žuqôä?�Âg]ø_‘<ëBõ·ŒÔ‹|5ÑE]ôÐD]4ÑE]4ÅìÂÒêìÅPŸ:-Ù󨻓µÿfòÀ¿Kn^xYá‹™Ú2õÑÑɽ+oL–lžŸ¼üæóÉ‘wª{ÚÿPÓÚ ‰ömßà†û1‘ü�¤Ø³.l¤(/*�ìu6ìHmØX0ëK¥_ûºaé­¥×Ù°QÅ>þPð¿i¤ß€áŸ}á_¬ó�©n4Ø}ï¸Ò�àÇ…¡ø-#þ7ŒØkjø�gÛ ?ùFiT±×ºpϺ(úõ¨ûweCá_MtÑD=4ÑDMtÑDM1»0„´ ÷#.¯-OrTÒ5ïŒäÊ9ÿuÀèa§Éó/t/fÚŒ_[[Ï7­½ÖÅ믬rcƒ=£bÍcSÝ�ŽØ¸a¿ÙÄžyQÏ ‰ý8ŠÝ¶} ûXö1ky��"¥g`Tøñ‘~¯{‘ o~ûoÓ£É/ØþÈHi¼¸gŒû5­ö¡¥ñ¢Â³.êy­ Ô‡|5ÑE]ôÐD]4ÑE]4ÅìÂÒvíÃýêZûñ•i�ýSrÙœÏ=Æÿø¿%3M²_[ë¿iíÇBìu/¶n˜ßïõ6ìÙõ¾Þ†½ð¨�öB¤óo;×�ö¬�u‹¾ã~ͬÖF5¿Å?è·ßÔáÆ ÿGžš] ìµ&Üxqç?ºAá­ï]Xšý¢�þvK¿"õèÇôã‰ÿ|J¯wQôÌ‹A~d„ƒ¼&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�Æ~TeëϹgqܱì:÷¬Žüèqé}™LíþÓ侇ÿ,YþÜíÉö][³÷®� á�¥„ÏÜðãFWlj…#F¹“!Q?nØëwظþê×ðµ6Šž�áŸÝPúÍ#þGH²_™Z0¦~®p„¨û4éÌc†ÁΣÓ>véGGÂ×¼x~©û|oß”»_w§øÇW]4ÑE]ôÐD]4Ѩ^Ìû Cˆ8ûõµ^]žÌ[w{2}á˜ä²î¿0|L˜÷…ä¶…_K}øŒdÓ#ïO<þËI²yRú"©ØÈà_TÔž½a¿ÍÄž½ñèÌ ’¹ÓÏrC… ECF¹“=ÛÃ~¤Å~´ÅÿXнžÇs‹nI^Zý£äõ5s“=¸ò G ûñ ÷,ŒìE<ý��4ýS?—ß»00üo)úñ‘ìW¥úCá·�ÔŠ|5ÑE]4ÑEM4ÑE]€êż¿0„ˆ±sYùÒãî7¹Ø —æG;Ù¯µ½çéiɲ-'ÛßÜ”$Ï_rìWâ>}frpÇŠÏà°­å7¦Ü>þW“YWÿVòãoýqòðŒ¿LÞöß’e?übòì¿þ}òü=ÿ�¼2g|òâ­éoq?ÞaCÆ´¿iê�á_£ß xþhBú,Œì7�Ø‹‹Ú€áeªýø‹ÿ’vÅ?¾šè¢‰.šè¢‡&šè¢‰.@õbÞ_B†™½@©½¾‡ý¦–ëþÊ€ÑÞb¿¾Öžb¯í±{ߎcÏâè¹*YÑõ›Éß~WòÀ”“̾ö7k8¾?æÝÉ]OLæ^÷ñäÁoüAòÄ œ<õ/’¬™zVòÜõŸL¶NþD²cⵞ޸þÏÓgbd¯…QúQ?bd¯ƒá_ijô,Œûµ©±ñ�¯&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�a`¿–öÑ�÷¸u±_Yþõ/“Ý_JæüøÿK�ûɳÿ—{ ûqûU¯EƒFþôýË~)¹sü¯%sÆ y`ÂIÉ£c#Y4îCÉÊñI6L85yqÂï%¿èøƒÂÁ¢èäGŒ7¿ó…ôÙ?üûôÇIº'—~œdËßM‡ ÿz;63dà_MtÑDMtÑCMtÑD z1ï/ !‘Ø œÚouñ/n:\}ãHþå†�$·|ã#ÉmWþFᨑ?ýà²ã’Ù¿”Ìÿ—w%=7¾+YöÍ÷&+'üViàxµã?Ž}SÎéÿÚ¹gdØoMqCFð#%µ¼&yMtÑDMtÑD=4ÑDMt)oÔ¨Q¥S¨···ßeÙ%©Ñ£G—.³?‡*½oOOÏ€Ûêêêr§PwwwÒÙÙ™½¥Å>û{´ª˜÷†�!f¯ùqçò)¥g|\ÞõÇÉM“Ë?³ãŽËþm2{̯&|ýד'Æ}0yjüo%Ï^þÑdË„ßK^¿ò?%‡æ½Ï½È; þm²ÿ¾¿è?f¬¸¿ôú‡·mL_ãÈáì3Zä5ÑE]4ÑE]ôÐD]4Ñ¥˜ ~€°ö6>?d„öíÁ¿&ì2]cö·3ØûŽ„!Äþùq'ÄÒ< !CìÖÅW¸Ä^ëcÎâk“LJŸùqçä“Ó_{˹É3—ÿ®{6ÇÎoýMéÇMì53J£†ý˜É®W“dÕYé ¢>yR’ìy&û8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢K±p ÙyùÂE—…*½¯©u±÷³Sø>áev¾bŠ®köçç?†ñŸ›¿ðm;Ùm;6�ØŸBšƒ!d-ÿùÏÒ×ý˜÷…¤oß÷Z6‚,˜õ%w¹�îGW&�™\÷ˆ;¯¬ç¾rlÙ»!;SyMtÑDMtÑD=4ÑDMt)Ï?àÙÈà€��W]uUáe^¥÷µ‘¡–!$ü¼ìýýø�¿¬ÜuíüüÇÊ+ú|íó´Ûðìs³ë?”0„4CȺcÙuny|ÓýÉÞ]Û’[ÇçNû÷ìp—ïúÁÅn±g€TÔwôïk#ÈÇ%É[K²3µp�×DMtÑDMtÑCMtѤÞe÷¬Kû½®àPžìGúóìA½=¸÷cDµCˆ]ßÞ¯Ú÷­uñム¯“¿¬Üu‹>û<üçlŸ�]n‡�ü’¿žÒ< !CÈ¿0êö][“ÖÌvÏ™wóÙî²wöïr„7®þ´ûsE«�¾� !›Êéêêr—yö>áõò·QéºÆnÏN¡îî³3{K‹}þö÷h'1ï/ !CdîÚ[Ýòت[Ý2ëÚS*¿PêÊO¦CÈŽ9Ù# yMtÑDMtÑD=4ÑDMt)–*F�=àA¿�g#…�&ÿ>ù·Ãá$™QBz{{Ýß±†�¡Å2Dî\>Å ! —~Û !ö:!»nÿªBÞî]œ]+s¨/Iž8.=Þ��9²p�×DMtÑDMtÑCMtÑD—bù¡Â<»Ü� ~¨È¿OÑØaoÛû]ViñÏ( ß'¼Ìß®[ò×µ?ûóóÃØß-|¿ðm;Ùm;öõ°?Ûߣ�ļ¿0„ ‘é Ǹ!dÑ‚kܲ`Ö—’7®ÿs7„Ùýzv­Œ= Äž ²ê¬ìŒ‘‡ƒ¼&ºh¢‹&ºh¢‹šh¢‹&º³øáŽ Æ� Æ.7Õ !~ì¨eñ·oÂg¦ä/+w];?ÿ±ò¿�—Fˆ}n~ôñC‰ÿ\ÚEÌû Cȹæ¡/§CÈcݲtÎ?¹ä�k?“]#`¯ bCÈæIÙ#yMtÑDMtÑD=4ÑDMò]Ö�—>‰qÚxQöAŽ6øÀ„�òï“ÛÔ3„„7¼Nþ²r×õŸ_È>2ìd£†]ž6òCHþvìó¯ßbÞ_Ž~GŽ<#a;÷<7„<1û¹!dí�ǹ!ä­ï]˜]#°üôôàð֒쌑‡|5ÑE]4ÑE]ôÐD]4Éw‰9„¬>;û •ŸÝaö#‚?ùQ Üûxö¶� E—…#‡WnÜ0ùËÊ]·h)Ç>'ÿy1„ óþrô;räQBÚçF�ñ?þ»dþmçº!äù¹—»!d÷�&d×毑į�aá{’äÈþìÌ‘‡|5ÑE]4ÑE]ôÐD]4Ñ¥X~¨°À� EÂ6Eø>ù·í}ìzž/ÿ¶É¿� þ:v™ÿœL¹ëÚ~ƒñ·QéGcìv‹>ßVóþÂ2vì~Å !öã1?ê<à !¿èžà†�}�ßš]+Ó¯b8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢K1ÿ@ߟÂAÀÞ¶‘ dC÷¿ýíCH¹Û0váåùqÅØy6@øëä‡�¢·‹®kÛŸo·—gŸwÑåþ|ÿ¹ùÛ±óíöB†CÈØôÚ7„Ø ¦ÞqʼnnyãžvCÈÁµf×ʼ8.B^¸,;cdâ ¯‰.šè¢‰.šè¢‡&šè¢‰.­#?Œ ùbÞ_B†ÀòŸÿÌ !w.»Ö� ]'$o}ÿËn9ôò³Ùµ2öLBì™!#yMtÑDMtÑD=4ÑDMti !C/æý…!d,è�í†�-N‡ûñ˜¾©ŸsCÈ;ûwe×ÊØkƒØrp[vÆÈÄA^]4ÑE]4ÑEM4ÑE]€êż¿0„ �ùëgº!dÎÏ.wCÈOnù 7‚¼qõ§³kdönHG�Å'fgŒ\ä5ÑE]4ÑE]ôÐD]4Ѩ^Ìû CȘ·îv7„ÜÿÓpCÈ‚þw7„¼ùí¿Í®‘ñ/”ºösÙ#yMtÑDMtÑD=4ÑDMtªóþÂ2f­¸Á !?™{±B–Üu‘Bvýàâì™Í“Ò!äùK²3F.òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!dø!¤{ÖnYy÷ÿtCÈžû'e×È<{~:„lø+–Fòšè¢‰.šè¢‰.zh¢‰.šèT/æý…!dÜÜ3.BºÎuCȺ;Ó!dß‚Ù52ËOO‡�Ý«²3F.òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!dL_8Æ !÷~ûL7„¼pGú«s¬¸?»ÆQïJ’'ŽK‡�#û³3G.òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!dL}t´Bî¼îwÝò‹[¿ä†�·{g×8jÏ3ébÏ iä5ÑE]4ÑE]ôÐD]4Ѩ^Ìû CȘ<ÿB7„Ü>á}nÙ1í\7„Þ¾)»ÆQÛïN‡{��ÀA^]4ÑE]4ÑEM4ÑE]€êż¿ :„Œ=:5j”;õôôdçöç/·Soo¯;/|¿ð²®®®~çÙÛµRB®|à‹ÉÿùÑÿãF�ï�}OòÆÕŸvCÈ;ûwe×8êÅqé²åê쌑�ƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºÕ‹y©8„ØHÑÙÙéþl#ˆ�yv¹3º»» ¯cˆ?ß®[ÏøRBìÙ cîúŒBf]óÛn±1¤Ÿuç¥CÈŽ9Ù#yMtÑDMtÑD=4ÑDMtªóþRq±ñ"|ˆ½íŸñáåϳgyä…cI» !—w} !ÝÓÎtCH_çç³K3O�–!û‚—Á8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷—A‡�päèèèðã1vž=Ä+KÂqÄF�ðGcÊý¸M%ÊCÈν¯º!ä?<Û !?½ñSnÙuûW³ke¿1ÆFû­1-‚ƒ¼&ºh¢‹&ºh¢‹šh¢‹&º”>& ÙcÉð2{¼²Ç›þ2ûs¨ÒûÚãÎümUóÒÛc]ÿSƒ±Û ¯kÏ>�ÁÞ¿ÜÇÈß^«‹yix±·Ão6;…ïS)^¹¥Œ !öER<­X·$B¾÷'n™÷Í?sCȶïþÏÒu¶<û7„Xô±~ïˉ'Nœ8qâĉ'NCqRb�ýa�'ýÿ±î‡Œð1§=õ�'í²ðÿ„·?ûÛì}íüj†»�ð1j¹‘¢þvò·™×ÈÇPUô=XÍ)–†4&/8yöÍZ+åg„¼ðú:7„L¿õÓnYtÛߺ!dÏý“²kõÚìô!ö:!-"æ7-ªGMtÑDMtÑCMtÑD—bá²óòƒ€8Š. Uz_SÍâ¯ïO6Pø‘Ÿ~Œð2»ípÐð×ÏŸì:yù�á?ÏðöìÏö¹úë„çûóì䯇·i·^fÿ­4Ì —˜÷—ŠCˆ}áüؾqûbÙuÃo$ûâç¿ÙBƒ]^Žò²éµ5n¹å»ŸrCÈÒ樂:wïÃ7f×8êç×§Cˆýæ˜ÁA^]4ÑE]4ÑEM4ÑE]ÊóÜCöxÒ#æÙãЫ®ºªð2¯ÒûÚÿFžŽáŸýež½í?‡·“¿Í¼üíØuíó o¯Òçá]ß ¿>áõ”ļ¿TBŒ}ñüÉ¡í›ÅÙÐ_žÿbÚuüÛ³óÂÛ¬‡ò²ò¥ÇÝrëwþ³Bž¹}”Bö=~kv�£Ö_�!Û® #yMtÑDMtÑD=4ÑDMê]n[r•{ìã4kÅ ÙG=ÆìÛcB?F„ÖC6 „Cˆ]ß?ž¬æ}íql#CHø8×nÃnËä/ ßo'›ögÿùÛeùÛñŸWx~¥ÏÃß–�Ê]?üû‡ï«$æýeÐ!D‘ò²lËÃîŽ~Û´ÓݲþÖ/¸!äÀʹÙ5ŽzúÌtykIvÆÈÇ?¾šè¢‰.šè¢‰.zh¢‰.šÔ»ÄB¦/“}Ôþì¹ * v~øÀބׯô¾&¼ÁÞÇT ò—…o‡·“¿Í¼üíøÏ+<¿Üçaçù¿C¥ëûû\†SÌû CH“-è�íîè]ÿòq7„lºù<7„\¿ »ÆQ=ǧCÈ¡¾ìŒ‘�|5ÑE]4ÑE]ôÐD]4Ñ¥X8HØvÿ¶=°÷Ï’ðì2ÿ@ß.³ë{~00ƒ½¯±Ëýˆaòo›üPPn€0ùË·ÃÛl|°÷³ÏųëÚÇo¯ÜçaçÙeÆþ\îúÆÞÎM”ļ¿0„4Ùüõ3ÝrÛUtCÈK7ÿ77„¼ýâòô ^JG�Å'¦o·òšè¢‰.šè¢‰.zh¢‰.šèRÌÀÛ å/Ë?˜/³“%Ì`ïkƒDx¹òl,ð—— Lþ²ðíüøÞfž�gã„ÿ¼üm„·—ÿXþóÿNöñÊ]ßø¯Oø5SóþÂÒdsÖÌH‡�‰'¸!äSÿ !‡wnM¯°s~:„¬:+}»Ep�×DMtÑDMtÑCMtÑD(ì™)Ã-æý…!¤ÉìE€l±ÄNo\û7„Ùýzz…—¦¥CHïÅéÛ-‚ƒ¼&ºh¢‹&ºh¢‹šh¢‹&º@‰=CDõÇbLÌû CH“u-½&¹ôÞsÜòý±ïq#ˆ�J^¸,B¶\��Ñ8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�&³WCs×gÜ2ëêßv#È×ÿyvéQ-ø«s yMtÑDMtÑD=4ÑDMtªóþÂÒdÓû§äëwžå†�»¯9Å !}�ŸÏ.=Ê^Ć�>Ý¿C=8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�&›<ÿÂäò®O¸!äÇßü/nÙuûW³K�ZzJ:„ìÛ”�Ñ8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�&»ò�/&n?Ó !so8#BîüÇìÒ£l±Ó‘ýÙ­�ƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºÕ‹yai2û�1WÞrºB½9ýÕ¹{^xà¥tY|búv á ¯‰.šè¢‰.šè¢‡&šè¢‰.@õbÞ_Bš¬ßò­?uCÈÞ‡oL/|kI:„¬8#}»…p�×DMtÑDMtÑCMtÑD z1ï/ !M´sï«né¼éÜòäMŸuCȾÇoM¯ðÚìtyæÜôíÂA^]4ÑE]4ÑEM4ÑE]€êż¿0„4‘B¾yÓº!dÉ·ÿ«B¬œ›^á¥iéÒ{qúv á ¯‰.šè¢‰.šè¢‡&šè¢‰.@õbÞ_Bšè…××¹!ä¦o§?óô·þÜ !×/È®pY:„l¹:}»…p�×DMtÑDMtÑCMtÑD z1ï/ !M´éµ5n¹%BÖ|3}FÈÛ/.O¯°ñ¢tÙÖ•¾ÝB8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�&Ê!«¯ý/n9¼}Sz…Õg§CÈÎùéÛ-„ƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºÕ‹yai¢e[vCÈ÷§ü®B6^÷'n9Ò÷Jz…§ÏL‡�7{Ò·[yMtÑDMtÑD=4ÑDMtªóþÂÒD~¹mÊGÓ!dÒénygÿ®ô KOI‡�ý›Ó·[yMtÑDMtÑD=4ÑDMt)ÖÓÓ“Œ5ªtêèèÈ.Iuuu¹óC½½½ýÞÇnÃäokôèÑ…ç‡#[ù�_ér»ÝðmûswwwöV1»¼³³3{«2û»ûëÚmÛǯæ}Ë}ŒðöÔż¿0„4Ñ€!dÂÇÜR²ð=ér¨/;£up�×DMtÑDMtÑCMtÑD—bù1!ÏÆ {ð 6øñăGÑmåÏ·ëÛy~äoË®çÇ‚Á.o×Î+ì6üçgjBBámäo3¯Þ�¡$æý…!¤‰ßt:„\s²BžŸð’7®þtzá;‡ÒÄN-ˆƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºË�!»ÌЇì+=«¡ÜmåÏ·ÁÆ„¢Ûòã‡ìr»öù}\]²ëù¿‹?Ïß~x¾¿­üuí4{öì~oÛuòòï—¿=ÿgûûùë„çûóìd~øù…—Ù+ 3õˆyai¢ùëg¦CÈ•'º!dë„ßOú:?Ÿ^h?c#È’“Ó·[ yMtÑDMtÑD=4ÑDMt)fcBøÀÛ? 7ög{nì2ž——¿-{ _t¾ÊÝ–J»<¼Ýrò#�Ýž]ß�þÏþü��gŸCþ6òoçåoË®kŸ«¿=Sîó]ß ¿6áõš%æý…!¤‰Š†�7¿ý·é…»W¥CÈS§¥o·òšè¢‰.šè¢‰.zh¢‰.šÔ»œwÛºä]—<åtѬ�ÙGøl�Pø`ß?ð€ç•»­üùvþTt[öqmìr»v*÷9åG ÿwðì}ó#…çÏËßFþmû³�~ÌÈß– Bv Ï/÷y[v*wýðk¾o³Ä¼¿0„4QѲëö¯¦öýœm±_¡Û‚øÇW]4ÑE]4ÑEM4ÑE“z—˜CÈÙÓWgµòx> ·“]Ï?¨/Ré¶ÂóýÛE·Ž ƒ]îoÇγÏÏþ›^ß” òç^þ6òoçåoËÿ=ÂóË}vžÿ;Wº¾±Ïa°Ï¥^1ï/ !MtÏÓÓÜò½Ë~É !;&ž‘ìúÁÅé…¯ÎL‡�gÏOßn1ü㫉.šè¢‰.šè¢‡&šè¢‰.Åò#…gºíÁwÈ� ö_{?Ï¿¹ÛÊŸo·m';??`ØõüPÍåþvís-òCA¹¢hhðçåo#ÿvž½Ÿ}Þž]7ÿ1Ê}vž]fìÏå®oìíðëÑL1ï/ !M4kÅ n±ÄNöcöÜ?)½ð•[Ò!dãEéÛ-†ƒ¼&ºh¢‹&ºh¢‹šh¢‹&º Ç„P~€0v={@nïc—û“½mòçÛ©èüpDÈ_–°_ér»,üÜí²¢QÀ®cïkŸ{¹¢hhðç ámæÙyöyä?çðcä?žÿ<ìcù÷³�YîúÆmì¿ÍóþÂÒDnùÑ_!›�þ׆ûo â ¯‰.šè¢‰.šè¢‡&šè¢‰.h5ƒ=3¥1ï/ !Mtëâ+’1w}Æ� wMü€Bö-˜‘^øü%éòÒ´ôíÃA^]4ÑE]4ÑEM4ÑE]Ðjì"Cñc1&æý…!¤‰¦/S~±‰±!dÛÐ|Ó 7òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!¤‰l¹¼ën¹ÿªßvCÈ�÷§®9'Bì·Ç´ òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!¤‰¦<ò¿KCH÷äSÒ!dåÜô•ŸL‡�ݫҷ[ yMtÑDMtÑD=4ÑDMtªóþÂÒD“ç_˜Lšñnypò©ny»wqzáÒSÒ!dÿæôíÃA^]4ÑE]4ÑEM4ÑE]€êż¿0„4‘ !WÞrºB¾úãéòâòôžãÓ!¤Eq�×DMtÑDMtÑCMtÑD z1ï/ !M4þÇ—\}ãÇÝòøUsCÈáí›Ò mYtBúçÄA^]4ÑE]4ÑEM4ÑE]€êż¿0„4Ñ%÷}6¹ö[uCÈ¢+Ó!äHß+é�ÃØb?Ó¢8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢ P½˜÷†�&²!äú©¿í†�¥¿ã†�wöïJ’=ϤCÈê³³k¶òšè¢‰.šè¢‰.zh¢‰.šèT/æý…!¤Iö½½Û !�S?â†�•ã?â†Ç~e® !ëÎKßnAä5ÑE]4ÑE]ôÐD]4Ѩ^Ìû CH“ìÜûªB¾Õ™þhL¿!dûÝé²ñ¢ôíÄA^]4ÑE]4ÑEM4ÑE]€êż¿0„4‰B¦_s’B6L85yãú?O/|å–tyq\úv â ¯‰.šè¢‰.šè¢‡&šè¢‰.@õbÞ_Bš$?„lœð±¤¯óóé…›'¥CÈ–«Ó·[yMtÑDMtÑD=4ÑDMtªóþÂÒ$›^[ã†�×ýÖÀ!¤÷âtÙÖ•¾Ý‚8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢Ky£F�*�B½½½ý.ëèèÈ.I�=ºt™ý9Té}{zzÜVWW—;©³ÏÛ>ÿVóþÂÒ$~ùîÄ÷¹!dë„ÿ˜¼9íoÒ ×_�!;æ¤o· òšè¢‰.šè¢‰.zh¢‰.šèR¬³³³4@Øüîîn÷g?d„úm°ë»Ì_רŸýí ö¾ÊCˆ}îùQ'ÄÒ| !M2pùýd×í_M/´_›kCˆýö˜ÅA^]4ÑE]4ÑEM4ÑE]Š…CHÈÎóÅ玢ËB•Þ×T;„ø÷±“¿¾ .vÛþûüç{Ûm· ¸ýü’¿ žÒ| !M’BvNü£d÷=cÒ m±S ã_MtÑDMtÑD=4ÑDMê]b!ón>;û¨ýÙ|? ~$°óÃñÁ„ׯô¾¦Ú!ijëúë7:„ oŸ!$óþÂÒ$ECÈžû'¥Ï±dñ‰Ù5[ÿøj¢‹&ºh¢‹&ºè¡‰&ºh¢K1?{Àïß¶ûöl <»Ì� vY8„Ä`ïkòÏÄÈ¿�g—›üðaoûËŒ�þvìã…¿Jüm䇻 ÿñüß«ÒçÙ*bÞ_Bšdþú™É˜»>ãF�™?plÙ¿9B–ž’]³5q�×DMtÑDMtÑCMtÑD—bþÁ½?…ò—…„ /³S8 ö¾68„—‡£Šg#†¿Ü�(ECˆ½í¯—ÿ86jäoÃ+º}ãÏ÷Ÿ“¿ ;ßnŸ!¤¹Bš$Bîʆ�½ߘ$o-I‡�•ŸÌ®Ùš8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢KkÊ#hŽ˜÷†�&)Bö-˜‘þÊ\BìWè¶0òšè¢‰.šè¢‰.zh¢‰.šèÒšB†FÌû CH“Üóô´dìÌO»!äá†�ýKïI’×ç¥Cȳçg×lMä5ÑE]4ÑE]ôÐD]4Ѩ^Ìû CH“ÌZqCry×'ÜÒ=ù7„X97I¶u¥CÈÆ�¿.ª•p�×DMtÑDMtÑCMtÑD z1ï/ !MÒo™t²B®_plyþ’ìš­‰ƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºÕ‹yai’›{ÆB®ø�BÞ~qy’lž”!ö߯A^]4ÑE]4ÑEM4ÑE]€êż¿0„4Éô…c’I3þÀ !ó¯øm7„zùÙcCÈÖ©Ù5[yMtÑDMtÑD=4ÑDMtªóþÂÒ$6„\yËényhbú£1Gú^I’M_K‡û™ÆA^]4ÑE]4ÑEM4ÑE]€êż¿0„4É5}ùØ2á¤tÙýzú"©6„l¿;»fkâ ¯‰.šè¢‰.šè¢‡&šè¢‰.@õbÞ_Bšdòü “É7ýžB{¢Bûµ¹6„ôé}ÎÍÄA^]4ÑE]4ÑEM4ÑE]4ÑESÌ.u!ɨQ£Ü©»»;;·¿Ñ£G—®ÓÓÓãγëúóìÔÙÙéί…êrí·>ê†�žqé3BœÕg3„`ØÐE]4ÑE]ôÐD]4ÑE]4ÅìR×bc† !¦··× y]]]¥‘ÃF{ßzÆ�ЈBV~2BöoNßnQL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìR×b#Hø,{Û?ãÃËŸgϱѤí†�¥§0„`ØÐE]4ÑE]ôÐD]4ÑE]4ÅìR÷Ž6l„ȱóìY!ž»^ø£1ù÷«†ârY÷_÷Bú¦œ“^°äät9¼;}»Eq0ÑDMtÑDMtÑCMtÑDMtѳË� !þGfÂSþY#ö¶�_+Å!ä’û>Ûéü|z�� vjqL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìR÷ùa¤ˆýhLÿ#3µ°!ľHJ§üòÚ7þÒ�o#È‘'~eÀõ9qâĉ'Nœ8qâĉ'NÇN±Ô5„Ø�¼ØøaʽXj(|áÔ��'å’JTŸrÝ7Ç !‹Æ}(}Fˆ½.ˆ=Ä^'¤ÅÅü¦Eõ袉.šè¢‰.zh¢‰.šè¢‰.šbv©ûg6Êýj\?lØøá/÷£‰±AÄŸo§ZŸ bT‡�)S>ì†�•ã? tÑDMtÑD=4ÑDMtÑDM1»Ô=„ §3„¼Ù“!«Ïήպ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤I ‡�¾£Ÿ'C†]4ÑE]4ÑEM4ÑE]4ÑESÌ. !M°kÿnéœzJiykÆ“dûÝéòìùÙ5[MtÑDMtÑD=4ÑDMtÑDM1»0„4Áν¯º!äÛ7üniÙuûW“d[W:„l¼(»fëâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�&ðCÈôkNrCÈÆ c�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bš`ëϹ!äæëÒ×±!d÷=cŽ^05B^¸,»fëâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�&ØôÚ7„̸îd7„<7ácÉžû'%Éæ£'Bì¿-Žƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BšÀ!ß�ø>7„l�ðû !�@MtÑDMtÑCMtÑDMtѳ CH¬~y¡B¾wÅ ¥!dïS“dÓ×Ò!ä¥iÙ5[MtÑDMtÑD=4ÑDMtÑDM1»0„4Á²-§CHDZg„ì[0#}‘TBìES[MtÑDMtÑD=4ÑDMtÑDM1»0„4�Bl±ÓΉÄ tÑDMtÑD=4ÑDMtÑDM1»0„4Á‚ÞÙ†�ýOÞ•$Ïœ›!;æd×l]L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒó×ÏL.ùÑ_öB¬œ›$«ÏN‡�>­Ïw(p0ÑDMtÑDMtÑCMtÑDMtѳ CHØ2æ®Ï¸ä® '0„@]4ÑE]4ÑEM4ÑE]4ÑESÌ. !M!w^þ>7„\û`’¬üd:„¼µ$»fëâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�&è7„Œÿ57„¼ýâò$YzJ:„ìßœ]³uq0ÑDMtÑDMtÑCMtÑDMtѳ CHÌY3#÷Ã?uCȽ>ÀtÑDMtÑD=4ÑDMtÑDM1»0„4Á¬7$—w} !ÝW|ˆ!2袉.šè¢‰.zh¢‰.šè¢‰.šbvai‚p™sù‰n9¼cs’,|O:„ÙŸ]³uq0ÑDMtÑDMtÑCMtÑDMtѳ CHôBƧ?s¤ï•t±Sà`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�&°!äÊ[NwCȃ?ÌtÑDMtÑD=4ÑDMtÑDM1»0„4A8„<Ôñï³!äçéòÄqÙµZMtÑDMtÑD=4ÑDMtÑDM1»0„4A¿g„Œ}¿BÜ ¤Úb/˜Ú8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤ n[rU2yúinytìo0„@]4ÑE]4ÑEM4ÑE]4ÑESÌ. !M0}á˜äÚo}Ô !=ãNb� ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bš py³'BV�•]«µq0ÑDMtÑDMtÑCMtÑDMtѳ CHä‡�¾)ç$IßÑÏц�Õgg×jmL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ×<ôåþCHççB �.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�&˜<ÿÂäÚié²h܇Ò!äÕ™é²áKÙµZMtÑDMtÑD=4ÑDMtÑDM1»0„4� !S¦|Ø !+Ç$B¶u¥CÈÆ‹²kµ6&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbvai‚ ó¾ÀÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !MpÉ}Ÿí7„¼yÓI²åêtyq\v­ÖÆÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !M�BvÝþÕ$Ù<)Bì¿m€ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bš€!„ƒ‰*ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÔ·o‡Bn¼ö$7„l˜pj:„<÷•tyå–ìš­�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]B´sï«n™~M:„lœð±t±Iµ!Ä^4µ p0ÑDMtÑDMtÑCMtÑDMtѳ CHƒŠ†�=÷Ob�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BTvYsN:„윟]³µq0ÑDMtÑDMtÑCMtÑDMtѳ CHƒÊ!«ÏN‡�>�Ïu(q0ÑDMtÑDMtÑCMtÑDMtѳ CHƒŠ†�½Le�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BT4„ì[0#Iž:-BönÈ®ÙÚ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤A[vnpCÈ-“~Ý ![&ü^:„,=%BöoήÙÚ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤A›^[ã†�ïN|ŸB¶Nø}†È ‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤AECÈ�•s“dá{Ò!äÈþìš­�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BTv±ÄNm‚ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÄ’â`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�ù!dƸ_vCÈ/:þ 9ðô¬té9>»Vëã`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�­~yaúŒ�~—;íœøGÉÁ·¦Cˆ½`j›à`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�-Ûòð€!äг÷2„@]4ÑE]4ÑEM4ÑE]4ÑESÌ. ! *BzÌ tÑDMtÑD=4ÑDMtÑDM1»0„4Ȇ�1w}Æ� 3Çþj:„¬Ÿ‘!«ÎÊ®Õú8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤Aárçø_ë?„¬>;»Vëã`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�-~ñ' !Gq0ÑDMtÑDMtÑCMtÑDMtѳ CHƒæ¯Ÿ™Œ�ùi7„üë忞½Xê7Ó!ä™s³kµ>&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbvai� !—w} !ÝW|È !‡×NJ‡��e×j}L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ p™3á7Ürä¹) !�@MtÑDMtÑCMtÑDMtѳ CHƒú !ã?à†�wžŸÊ tÑDMtÑD=4ÑDMtÑDM1»0„4¨pÙpi:„lž”]«õq0ÑDMtÑDMtÑCMtÑDMtѳˠCÈèÑ£“Q£F¹SOOOvnþr;õööfçŸßÕÕÕï|{»VJCÈÏþ ¹ò–ÓÝòàøôGc’MãB �.šè¢‰.šè¢‡&šè¢‰.šè¢)f—ŠCˆ����îÏ6‚Ø(’g—û1£»»»t;Ï'ö玎ŽÒŸýõë¥4„ÌZqñ!dìûÓ!dóÑ¿+CÐE]4ÑE]ôÐD]4ÑE]4ÅìRq±Q#|ˆ½>ãÃäϳgyäÙm´Õb¯ bCȶÆþž# MtÑDMtÑD=4ÑDMtÑDM1» :„„#‡�ù�±óì™ ^ÑX>kÄþþhLþöª¡<„¼qígB ƒ.šè¢‰.šè¢‡&šè¢‰.šè¢)f—†‡{;6ì¾�]n·S$üQšZ(!}�Ÿg� ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÂá£èÙyá°a×µa¤’Á./bCˆ}‘N3\‘\}ãpCÈcã~3yí™ì}ò�ÝòÒ33 ߇'Nœ8qâĉ'Nœ8qâÔÿKÅ!Ä~¤e°K …?ãG�JÉ=#Ä¿vH-Ôžrí·>ê†�žq'%oÞtA’¬>;}FHŸÎç9Ôb~Ó¢ztÑDMtÑD=4ÑDMtÑDM1»TBŒÿq—pÔ°±Ã6~øËýhbìòð}ídïŸ?¿ÊCȮۿÊtÑDMtÑD=4ÑDMtÑDM1» :„(RB¦/3pYzJ:„ìßœ]«õq0ÑDMtÑDMtÑCMtÑDMtѳ CHƒBRL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ †�MtÑDMtÑD=4ÑDMtÑDM1»0„4Ȇ�ëoøˆB–Œûp²ëÎd� ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BdCÈ”)vCÈÊñIöÜ?‰!2袉.šè¢‰.zh¢‰.šè¢‰.šbvaiCHŠƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÄ’â`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�]óЗ!O—!ïÊ®Õú8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤A“ç_8p±ÄNm„ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÄ’â`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�ÙrÃutCÈšË'Ùû“o0„@]4ÑE]4ÑEM4ÑE]4ÑESÌ. ! ²!dÚUïwCÈÆ Kö-˜ÁtÑDMtÑD=4ÑDMtÑDM1»0„4(?„ì_pC:‚,:!»F{à`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†� B®M‡ûºm„ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BtéýÅrMtÑDMtÑD=4ÑDMtÑDM1»0„4è’û>›B®f� ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]B”B,¸4BVŸ�]£=p0ÑDMtÑDMtÑCMtÑDMtѳ CHƒlùÖ•'¸!dÓ„�'Ÿø:CdÐE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ B¦Ox¯B¶Nøýäí§²×a�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]B4`Y6>BÖ�—]£=p0ÑDMtÑDMtÑCMtÑDMtѳ CHƒòCÈ¡¥ÿ˜!/Ê®Ñ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤»ö¿á†�›;~•!„ƒ‰$ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]B°sï«ÙrB^›�]«=p0ÑDMtÑDMtÑCMtÑDMtѳ CH ‡�ŸJ‡�>�Ï1&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbvai€ !ãîø”Bf�=Þ !ÉÊ?c� ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]B`CÈå]ŸpCÈýc²!dõÙ !�AMtÑDMtÑCMtÑDMtѳ CH ‡�e§¦CȾMÙµÚMtÑDMtÑD=4ÑDMtÑDM1»0„4`Éæù‡�¥§¤CÈþÍÙµÚMtÑDMtÑD=4ÑDMtÑDM1»0„4`þú™ !&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbvai@á²øÄt9¸-»V{à`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�!6‚Ø©Íp0ÑDMtÑDMtÑCMtÑDMtѳ CHl™ø½3Ü2ïëïKÞ¸ö3 !�BMtÑDMtÑCMtÑDMtѳ CHl¹ò–ÓÝòàØ÷'}�Ÿg�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BÀr MtÑDMtÑD=4ÑDMtÑDM1»0„4 ?„¼õ�¿HG�%'g×hL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ€Y+nÈ !ÿw:„دÐm3L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ†�c8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤ !Çp0ÑDMtÑDMtÑCMtÑDMtѳ CHl™<ý47„<:ö7’]ßý+†H¡‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤6„\û­�º!¤gÜIÉÞ»ÎK‡�gd×hL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ€CȬsÓ!dõÙÙ5ÚMtÑDMtÑD=4ÑDMtÑDM1»0„4€!ä&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbvaiÀô…cB2L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂÒ†�c8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤ !Çp0ÑDMtÑDMtÑCMtÑDMtѳ CHl¹î›§¸!dɸ'û~ô×éò̹Ù5ÚMtÑDMtÑD=4ÑDMtÑDM1»0„4À†�)S>ì†�•ã?’øÉÿ›!/Ê®Ñ>8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤ !Çp0ÑDMtÑDMtÑCMtÑDMtѳKÓ‡�ÎÎÎdÔ¨QîÔÕÕ•�Û_GGGé:ÝÝÝÙ¹ÕcÑÃÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ.MBzzz’Ñ£Ggo%nèèííÍÞJÙðaCˆ±ËŠ®3•!䚇¾Ì’á`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f—¦!öl�ðY övþùól±¥*CÈäù2„d8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ¥éCH8rØ(’ÿñ{Û®ç�%ƒaÑÃÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ.чã_ÄŸêBì‹4ܧ‰?•ÜpÝݲúòS’7çœã†�7—�*¼>'Nœ8qâĉ'Nœ8qâÄ©øKô�ɳ�©¯bÏ™vÕûݲqÂÇ’C��›>#dó¤ìí#æ7-ªGMtÑDMtÑCMtÑDMtѳKS‡={±Ô�]ß¿pj-Bôp0ÑDMtÑDMtÑCMtÑDMtѳKS‡Sô«qíÅPímcçùËÃѤ*CÈØ¹ç1„d8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ¥éCH *CÈ%÷}67„|&B^¹%»Fûà`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�ä‡�Ã�*B¶ |�ØVÇÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. ! °!dú„÷º!dë„ßO?ù !�BMtÑDMtÑCMtÑDMtѳ CHòCȑŖ!¯ÎÌ®Ñ>8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤NísCÈM~娲èôtéÓjbâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:íÜûªBnî8ž!ä(&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©CHL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR'?„ÌóKnÙ1ñ“w}$Bö<“]«}p0ÑDMtÑDMtÑCMtÑDMtѳ CH�üb#ˆ�vNü£ä�EJ‡�ý›³kµ&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©CHL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR'†�þ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N !ýq0ÑDMtÑDMtÑCMtÑDMtѳ CH�І�dá éòöŽìZ탃‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêT8„Øb§6ÄÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !u²!äŸïùs7‚üàë¿ÌÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !u²!dÌ]ŸqCÈ�cßËÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bê4`¹âŒtyâ¸ìí…ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bê”Bú®ýýtYzJv�öÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêÄÒMtÑDMtÑD=4ÑDMtÑDM1»0„Ôéå7Ÿ?6„|ý= !L$ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR§M¯­96„Œy7CItÑDMtÑD=4ÑDMtÑDM1»0„Ô‰!¤?&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©CHL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR§ü²ë›K‡�Õgg×h/L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR'†�þ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N !ýq0ÑDMtÑDMtÑCMtÑDMtѳ CH�Ö¾²¸ÿ2ý?1„@]4ÑE]4ÑEM4ÑE]4ÑESÌ. !uZ¶åá~CÈîï}’!r袉.šè¢‰.zh¢‰.šè¢‰.šbva©CHL4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR'†�þ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N6„\ÞõÇn¹oÌñÉÞüa:„løRv�öÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !uJ‡�O¸!äþ1Ç'ûîø£tÙxQv�öÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !uÊ!ûgÿW†È¡‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N ŸŸËà`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:Í_?“!$ÀÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêÄÒMtÑDMtÑD=4ÑDMtÑDM1»0„ÔɆ�I3þÀ !Ž}rð§Ù²¹#»F{á`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:Ùrå-§—†�·çû!dRv�öÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bê4wí­ !&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©Ó¬70„8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N†�‡þ8B^š–]£½p0ÑDMtÑDMtÑCMtÑDMtѳ CH�òCÈ¡‡ÏH‡�m]Ù5Ú MtÑDMtÑD=4ÑDMtÑDM1»0„ÔɆ�«oü¸Bž÷ÁäЂ?a�ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêdCȵßú¨BzÆ�”~üS !�CMtÑDMtÑCMtÑDMtѳ CH�Búã`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:ݱìºÜrf:„l¿;»F{á`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:M_8¦ßräñ�§CHßðnÃ�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêÄÒMtÑDMtÑD=4ÑDMtÑDM1»0„ÔɆ�ë§þ¶B–Žÿ0CItÑDMtÑD=4ÑDMtÑDM1»0„ÔɆ�)S>ì†�•ã?ÂÂÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bê4yþ… !&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©ÓÀ!äÔty³'»F{á`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:Ù2õú¹!dõå§$ï<þ�tÙ¿9»F{á`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f—A‡�Ñ£G'£F�r§žžâg;øËíÔÕÕåÎ ßÏŸz{{ÝåáyþúµPB¦]õ~7„lœð1†&’袉.šè¢‰.zh¢‰.šè¢‰.šbv©8„ØHÑÙÙéþl#ˆ�yv¹3lè°q#ÏÎ÷ïk×­gü1„èá`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f—ŠCˆ�á³@ìm5Bá°¡p,i•!dìÜóBL4ÑE]4ÑE]ôÐD]4ÑE]4Åì2è…?c×ó?êR$<ßF];•ûq›J†�Kîû,CH€ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]Bìr?hØý�Òx6|äÏ󺻻 ŸA2†=L4ÑE]4ÑE]ôÐD]4ÑE]4Åì2è2Ø�ÆäÇ‘¢÷É�'!OjeCˆ}‘†ó”B/xŸB^XÿDáõ9qâĉ'Nœ8qâĉ'NåO±TBì™þÙ6f={Ãγgvx6lø±Äη¡¤œÁ./Gñ!îÙ vjS1¿iQ=ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]}änÆ?ù�Ã~ÜÅ6�„× _Õ®Ž$ÆÎ ¯_†=L4ÑE]4ÑE]ôÐD]4ÑE]4Åì2"¹3„èá`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:1„ôÇÁD]4ÑE]4ÑEM4ÑE]4ÑESÌ. !ubé�ƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢‹¦˜]BêÄÒMtÑDMtÑD=4ÑDMtÑDM1»0„Ô‰!¤?&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©S8„á½nyyòÇB8˜H¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤N !ýq0ÑDMtÑDMtÑCMtÑDMtѳ CH�Búã`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:ìÜûªBnîøU7„lû—SÒ!dÕYÙ5ÚMtÑDMtÑD=4ÑDMtÑDM1»0„ÔáØr|ÿ!dõÙÙ5ÚMtÑDMtÑD=4ÑDMtÑDM1»0„Ô�!d &šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©ƒBnûn7„ì¸áwB8˜H¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤~±ÄN»¾ù1†&’袉.šè¢‰.zh¢‰.šè¢‰.šbva©CÈ@L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR‡ü²{zö!ë/È®Ñ~8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤/¼¾®ß²çæ�¤CÈÆ‹²k´&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©Ã¦×Ö0„äp0ÑDMtÑDMtÑCMtÑDMtѳ CHBâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:Øré½ç¸äÖþ¿BŽâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè¢)f†�:Ø2æ®Ï¸!äÎ1ïNöÞö;é²ékÙ5ÚMtÑDMtÑD=4ÑDMtÑDM1»0„Ô!?„쟕ýúÜÍ“²k´&šè¢‰.šè¢‰.zh¢‰.šè¢‰.šbva©CÈ@L4ÑE]4ÑE]ôÐD]4ÑE]4ÅìÂR‡å?ÿCHMtÑDMtÑD=4ÑDMtÑDM1»0„ÔaÙ–‡Br8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºhŠÙ…!¤ !q0ÑDMtÑDMtÑCMtÑDMtѳ CH !÷|4B~~}v�öÃÁD]4ÑE]4ÑEM4ÑE]4ÑEÓ÷]›ýiè1„Ô!?„¼ïätÙÖ•]£ýp0ÑDMtÑDMtÑCMtÑDMtÑñRß�dÒüÍÉ)“—&ïºäñìÜ¡ÇR†��8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&º ¯ý‡Ž$3—¿šœsó7~øÓÇ&/Ê®1ôBêÀ2MtÑDMtÑD=4ÑDMtÑD—áÑó›ÉWîy.9~lOiü8aü¢äk÷mJ–oݵ CHBâ`¢‰.šè¢‰.šè¢‡&šè¢‰.šè�ýèËÕmIN½fY¿g|nÆÚäî•ÛݳC¼˜]BêÀ2MtÑDMtÑD=4ÑDMtÑD—¡eã†�6v„ㇽˆ�"6Ž‰Ù…!¤ !q0ÑDMtÑDMtÑCMtÑDMtK¶¼•\|o¯ûq?~¼ç²…ÉE³6&�mêË®U^Ì. !u0„Ìù÷éòêÌì퇃‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢KólÞ¹¿ðG_>9merë’_$»ή9¸˜]Bê�BÍû@:„ô ïç5œ8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&º4¦oß!7rœuãª~ãÇÉW.IÆÍ{1Ù°}ovÍÚÄìÂR†��8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢‹&ºÔîБw’9kw$çw=ë~ÜÅ�ö`¾tç†ä‘çÞp×iDÌ. !u`ˆƒ‰&ºh¢‹&ºh¢‹šh¢‹&ºh¢Kõìu?ì×Ûž8aqiü8îÒ'’sn^“Ì\þjM?ú2˜˜]Bê`Cȸ;>å†�{ÇüJrè�2„p0‘DMtÑDMtÑCMtÑDMt©¬Üë~œ1uE2uÁÖ²¿õ¥Q1»0„ÔÁ†�Ë»>á†�ûÇŸ~ðC !L$ÑE]4ÑE]ôÐD]4ÑE]ªôº—Í}!YõòîìšC'f†�:0„ ÄÁD]4ÑE]4ÑEM4ÑE]4Ñ%e¯é1oÝëCúºµˆÙ…!¤K6ÏÏ !'¥CÈ›=Ù5ÚMtÑDMtÑD=4ÑDMtÑÔî]–oÝ•\2çù²¯û±ÿБìšqÅìÂR‡ùëgöBŽ<úké²sv�öÃA^]4ÑE]4ÑEM4ÑE]4µc{]�ëýyrÚuO•Æ;�>eù�¾îG-bva©CÈ@ä5ÑE]4ÑE]ôÐD]4ÑES»t±ßèÒµl›{¦G8~œ4ñI÷Œ�¯ûQ‹˜]BêÀ2yMtÑDMtÑD=4ÑDMtÑÔê]ìµ=ì5>ìµ>üøa¯rÁëÝk‚Ä|Ý�ZÄìÂRB&~ï 7„üôë'$G~ö>†ò’袉.šè¢‰.zh¢‰.šè¢©»Ø�¶Lš¿99eòÒ~Ïþ°ßc¿ Æž¢.f†�:Ørå-§»!äÁ±ïOÞYpCyItÑDMtÑD=4ÑDMtÑÔ*]ìEMï^¹=ùÜŒµýÆClÙ¼svÍ‘!f†�:0„ ÄA^]4ÑE]4ÑEM4ÑE]4�ô.öÚ_»oS¿ßúâôÅ~,f¤ŠÙ…!¤ !q�×DMtÑDMtÑCMtÑDM#±Kß¾CÉw¾œœÙùt¿gœ1u…;Çž·³kŽ\1»0„Ô!?„¸ÄNmŒƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºhI]ìöL{Ƈ?N¿È=#dùÖ]ÙµZCÌ. !upCÈMÿÑ !�Ž=‘!ä(òšè¢‰.šè¢‰.zh¢‰.šè¢I½‹=»cê‚­Éi×=ÕïÙgO_�Ì\þª{m�V³ CHl¹ö[uCHϸ“BŽâ ¯‰.šè¢‰.šè¢‡&šè¢‰.šT»,ÙòÖ€gœ|å’¤ã�‘÷§õˆÙ…!¤ !q�×DMtÑDMtÑCMtÑDMJ]ükœ>eyiü8îÒ'’so}&™·îõäБw²k¶¾˜]BêÀ2yMtÑDMtÑD=4ÑDMtѤÐå™_ìI.¾·79~lO¿gد½}©ï@v­ö³ CHBâ ¯‰.šè¢‰.šè¢‡&šè¢‰.š†«‹=»ãî•Û“³n\U?ìô¹k“ùv¶Õ³?ŠÄìÂR†��8Èk¢‹&ºh¢‹&ºè¡‰&ºh¢‹¦Ø]ìÇ_®ôçÉIŸ,�öLûÍ/¶ïÍ®…˜]ê~ôÞÑÑ‘Œ5Ê�º»»³sû=zté:���î<»®?/<¿ CÈ”)vCȪ+>œŽ KOÉ.mOä5ÑE]4ÑE]ôÐD]4ÑES¬.ö§—Ìy¾ß�¿œzͲdÚã/¹qýż¿Ô5„ؘaCˆéííuƒF^WWW¿‘îc×µ÷­gü1„èá ¯‰.šè¢‰.šè¢‡&šè¢‰.š†ºËª—w'_ºsƒ{ÑS?€Ø�ÃÌY»#»ŠÄ¼¿Ô5„Ø> ÄÞîééÉÞJå?–´Â2oÝí !9ä5ÑE]4ÑE]ôÐD]4ÑEÓPu±ä¼ÛÖ•ÆBÎïz6Y¾uWv TóþR÷6l„ÈþøŒ=ÄØõüyv*z¿Á ÷2kÅ !9ä5ÑE]4ÑE]ôÐD]4ÑES³»<¶©/9çæ5¥ä=—-t?³iǾì¨FÌûË�!~è°× ±÷ɳ۰ëÔŠ!DyMtÑDMtÑD=4ÑDMtÑÔ¬.6€œ=}ui9aü"÷ëo·í:˜]µˆy©{ ‡�ü0bòãHþ}<Iü³EªeCˆ}‘†ë4cÁ¥!dõäô·Æì_|záu9qâĉ'Nœ8qâĉS뜺¯Kþô†%¥äÄñ “ºkE²|í†ÂësªþK]Cˆ½ª†G¹KµËízž ù±ÄÞ¶ók¥ôŒ�5×|0}FÈê³³KÛSÌoZT�.šè¢‰.šè¢‡&šè¢‰.šêí’ ûu¸ö`ö:’]�ˆy©k1á¯Æõ‡ÿ1ãò/�jÿ ϯõÙ †!DyMtÑDMtÑD=4ÑDMtÑTk{­�‹fm, þG`ø¸ÍóþR÷2œ†{™¾p CHyMtÑDMtÑD=4ÑDMtÑTm—Í;÷»Äÿ\{Ôqó^d"1ï/ !u`ˆƒ¼&ºh¢‹&ºh¢‹šh¢‹&ºh¬‹½Ø©ýÖ>üboó"¨C+æý…!¤ý†�k#Bž97»´=q�×DMtÑDMtÑCMtÑDMåºØk}\ýÐ–äø±=n±g‚Ø3Bø5¸qļ¿0„Ô!BÖNù@:„l¼(»´=q�×DMtÑDMtÑCMtÑDME]î^¹=9eòÒÒë€\pÇz�ÈbÞ_BêÀ2yMtÑDMtÑD=4ÑDMtÑvY¾uWrÖ�«JÈ™�O'=/¼™]Š˜bÞ_BêÀ2yMtÑDMtÑD=4ÑDMtÑd]ìõ>Âßc¿ ·kÙ¶ì1ï/ !u¸æ¡/3„äp�×DMtÑDMtÑCMtÑD=ö: ÿt׊Òë€Ø ¡v<°9Ù}àpv —˜÷†�:Lž!CHyMtÑDMtÑD=4ÑDMtÑ2oÝë^Ä~E.4ļ¿0„Ô�!d òšè¢‰.šè¢‰.zh¢‰.šè¢á¥¾Éù]Ï–�Ó¯]Ì뀊ya©ƒ !S¯ÿ�Bžý—BŽâ ¯‰.šè¢‰.šè¢‡&šè¢‰.ÃëБw’ï,|99aü"7€Ø�ÃL{ü¥dýFº(Šya©ƒ !Ó®z¿B^èü CÈQä5ÑE]4ÑE]ôÐD]4Ñeø¬zywòÉi+KÏ9ï¶uî™!†.šbva©CÈ@L4ÑE]4ÑE]ôÐD]4Ñ%>{ÑÓËæ¾�wén9ùÊ%Éœµ;²KStѳ CHBâ`¢‰.šè¢‰.šè¢‡&šè¢‰.q…/†jCÈ%sž/üm0tѳ CH.ëþk†�&šè¢‰.šè¢‰.zh¢‰.šèGß¾CÉE³6–~ æÌΧ“å[we—DM1»0„Ôá’û>Ë’ÃÁD]4ÑE]4ÑEM4ÑE]†ž= Ä~üÅ�÷\¶0™º`«{‘ÔJè¢)f†�:0„ ÄÁD]4ÑE]4ÑEM4ÑE]†NþY ö¨¶ïÍ.­Œ.šbva©CÈ@L4ÑE]4ÑE]ôÐD]4ÑehÌ^ýZrâ„Å5= $DM1»0„Ô�!d &šè¢‰.šè¢‰.zh¢‰.šèÒ\;ö¼�œßõl]Ï ÑESÌ. !u`ˆƒ‰&ºh¢‹&ºh¢‹šh¢‹&º4�½ÈIŸ¬ûY !ºhŠÙ…!¤ !q0ÑDMtÑDMtÑCMtÑD—ÆÙ¯¿½øÞÞ†Ÿ¢‹¦˜]Bê!/~ûÄtyá²ìÒöÄÁD]4ÑE]4ÑEM4ÑE]³dË[É©×,sÈq—>‘\ýЖºŸ¢‹¦˜]BêÐoùÎûÓ!dó¤ìÒöÄÁD]4ÑE]4ÑEM4ÑE]êccǤù›Ýøa#Èi×=•¬zywviãè¢)f†�:0„ ÄÁD]4ÑE]4ÑEM4ÑE]jg?örfçÓ¥…ùÚ}›’ý‡Žd—6]4ÅìÂR†��8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢KmnYüJrüØ7€œ|å’dþ†�Ù%ÍEM1»0„Ô�!d &šè¢‰.šè¢‰.zh¢‰.šèRûµ¸çݶ®ô,û¹}ûe—6]4ÅìÂR†��8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢Ëàyî ÷ì@ìÙ 3—¿š]2tè¢)f†�:0„ ÄÁD]4ÑE]4ÑEM4ÑE]ʳD½lî ¥g�دÅݼsvéТ‹¦˜]Bê`CÈ��wCÈ/nýõtyiZvi{â`¢‰.šè¢‰.šè¢‡&šè¢‰.ÅìQϘºÂ ö›aì7Ä4ã×âV‹.šbva©ƒ !Ó'¼× !¯ÿà„tÙÖ•]Úž8˜h¢‹&ºh¢‹&ºè¡‰&ºh¢Ë@á ¢ž2yi²dË[Ù%ñÐESÌ. !u`ˆƒ‰&ºh¢‹&ºh¢‹šh¢‹&º“AÔ/ݹ!Ù}àpvi\tѳ CHBâ`¢‰.šè¢‰.šè¢‡&šè¢‰.©ðQO¿(¹{åöì’áAM1»0„ÔhçÞWÝrsGú! !)&šè¢‰.šè¢‰.zh¢‰.šÚ½KþQÏž¾:y©ï@véðáþ¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šè¢©�»ä_õúGõQ+áþ¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šè¢©]»„/ˆzÚuO%Ë·îÊ.ÑÀýESÌ. !5òCÈ-cßí†�Ýwÿ»tÙ1'»F{â`¢‰.šè¢‰.šè¢‡&šè¢©Ýºôí;”œßõléGa.¾·wØ^µî/šbva©‘Bl±ÓÛsŽO‡�¾áûœp0ÑDMtÑDMtÑCMtÑÔN]ì×àگõÄ^uÎÚÙ%z¸¿hŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&ºhj—.öúö: 6‚œuãªdóÎýÙ%š¸¿hŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&ºhjõ.ÛvLιyMéGaÆÍ{QæQ+áþ¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šè¢©•»Ìß°39iâ“n±ÿÚÛ#÷M1»0„Ô(?„šû«éòfOv�öÄÁD]4ÑE]4ÑEM4ÑES+v±g|Ø3?ü³@ì!öÌ�‘„û‹¦˜]Bj”BŽ<øîtÙ¿9»F{â`¢‰.šè¢‰.šè¢‡&šè¢©Õº¼ÔwÀ½ˆ öš “æo? “ÇýESÌ. !5²!äÒ{Ïq#Èm—ý_É‘™!ä(&šè¢‰.šè¢‰.zh¢‰.šZ©‹ýèˉ—~æ‘çÞÈ.y¸¿hŠÙ…!¤F6„Œ¹ë3n¹sÌ»“#¿—!ä(&šè¢‰.šè¢‰.zh¢‰.šZ¡K+ü(L÷M1»0„Ôˆ!¤MtÑDMtÑD=4ÑDM#½Kø£0vêx`dþ(L÷M1»0„Ôˆ!¤MtÑDMtÑD=4ÑDM#¹Ëc›úJ? cÿI¿f0Ü_4ÅìÂR£ü’<öoÒ!ä�CÙ5ÚMtÑDMtÑD=4ÑDM#µËw¾ì^ ÕF{Fˆ=3¤•pѳ CH� !6‚Ø©Íq0ÑDMtÑDMtÑCMtÑ4ÒºØ�½|åžçJ? óµû6µÄ�Âäqѳ CH�BŠq0ÑDMtÑDMtÑCMtÑ4’ºØ  ~rÚJ7€¼ç²…IײmÙ%­‡û‹¦˜]Bjô‹·63„à`¢‰.šè¢‰.šè¢‡&šè¢i¤tY¾uWrò•KÜrÊä¥îíVÆýESÌ. !5ÚôÚ†�L4ÑE]4ÑE]ôÐD]4�„.öÌ{ˆ� ö«qwìy;»¤uqѳ CH�BŠq0ÑDMtÑDMtÑCMtѤÜÅ^ûã²¹/”^ÄþÜŠ¯R„û‹¦˜]BjÄRŒƒ‰&ºh¢‹&ºh¢‹šh¢‹&Õ.}û%Ÿ›±Ö Ç�íIî^¹=»¤=pѳ CH�BŠq0ÑDMtÑDMtÑCMtѤØeÃö½Éi×=åF�“&>Ùò¯R„û‹¦˜]BjÄRŒƒ‰&ºh¢‹&ºh¢‹šh¢‹&µ.óÖ½îžb#ˆý†ûM1íˆû‹¦˜]BjÄRŒƒ‰&ºh¢‹&ºh¢‹šh¢‹&¥.W?´%9îÒ'Üò¥;7$ûÉ.i?Ü_4ÅìÂR£ ¯.g)ÀÁD]4ÑE]4ÑEM4ÑE“B—Ý»áÃBliwÜ_4ÅìÂR£e[f)ÀÁD]4ÑE]4ÑEM4ÑEÓpwy©ï@rÆÔ¥Eµ�÷U1»0„Ôˆ!¤MtÑDMtÑD=4ÑDMÃÙå±M}îÅPm9õšeîER‘âþ¢)f†�Ù2îŽO¹!äÞ1¿Â’á`¢‰.šè¢‰.šè¢‡&šè¢i¸ºÜºä¥×9çæ5î×åâî/šbvôüèÑ£“Q£F¹SOOOvnþr;uuueçö?¿··×�g—‡ç‡×¯Öp!—w} !÷�9ž!$ÃÁD]4ÑE]4ÑEM4ÑESì.‡Ž¼“\|o¯@ìtÉœçÝyè�û‹¦˜]*>‚·‘¢³³ÓýÙFEòìr?fØØaㆱóüpbîèè(ýÙ_¿^ !z8˜h¢‹&ºh¢‹&ºè¡‰&ºhŠÙÅ~îÙÓW»Äž Òµl[v ò¸¿hŠÙ¥â#x>Âg�ØÛþ™^8lØeEc‰ÝCHkã`¢‰.šè¢‰.šè¢‡&šè¢)V—U/ïNN™¼Ô� öº K¶¼•]‚"Ü_4Åì2è6fýxŒ]Ïÿ¨K‘ðY#ö_];ÝÞ`T†�9_φ�'ŽË.m_L4ÑE]4ÑE]ôÐD]4Åèr÷Êíî7ÂØrfçÓî7Å 2î/šbvixñ?cçÛý�Òxv~ѳDLwwwÙË*±!ľHÃqš»ô‡ÉÄ[ÿ³BæŽO‡�·{>\x]Nœ8qâĉ'Nœ8qŠÓú�Ï%£ï|ºôz só²dÕº�…×åÄi¤œbt ‡�ü0bòãHø>~$©d°Ë‹ ÷3B®¼åt7„Sz=�«Ú’]‚jpѳKÅ!ÄžÝ1Ø‹¥ÚyöÌφ @ü’NBö~þµCjÁ¢‡ƒ‰&ºh¢‹&ºh¢‹šh¢‹¦¡è²aûÞä´ëžr#ˆýH̼u¯g— ZÜ_4ÅìRq16fø“5ìu>ü€aIxÿZ vyx¾�ìýóç׃!DMtÑDMtÑD=4ÑDMÍî2ÃÎä„ñ‹Ürê5ËÜ(‚ÚqѳˠCˆ"†=L4ÑE]4ÑE]ôÐD]45³ËÔ[Ý�ÁØò¹k“¾}‡²KP+î/šbva©CH1&šè¢‰.šè¢‰.zh¢‰.ššÑeÿ¡#ÉE³6ºÄNãæ½˜:òNv)êÁýESÌ. !5*B–�š]Ú¾8˜h¢‹&ºh¢‹&ºè¡‰&ºhj´Ë¶]“ON[Yz=�™Ë_Í.A#¸¿hŠÙ…!¤FáòðÄ—!«ÏÎ.m_L4ÑE]4ÑE]ôÐD]45ÒeùÖ]ÉIŸt#ÈÉW.IV½¼;»�âþ¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šè¢©Þ.w¯Üîžb#ÈÙÓW»g† y¸¿hŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&ºhª§Ë¤ù›K¯rñ½½¼Èàþ¢)f†�-è�ÍR€ƒ‰&ºh¢‹&ºh¢‹šh¢‹¦ZºØ‹¢~éÎ n±ß3íñ—²KÐlÜ_4ÅìÂR£ùëg2„à`¢‰.šè¢‰.šè¢‡&šè¢©Ú.ùE�¿agv †÷M1»0„Ôˆ!¤MtÑDMtÑD=4ÑDMÕtyæ{’S&/u#ˆýwÃö½Ù%*Ü_4ÅìÂR£~CÈ !MtÑDMtÑD=4ÑDMƒuylS_éEQϺq/Š ÷M1»0„Ô(B¹òøtÙxQviûâ`¢‰.šè¢‰.šè¢‡&šè¢©R—Ù«_s¯b#ÈE³6ò¢¨qѳ CH�BŠq0ÑDMtÑDMtÑCMtÑT®Ëw¾\A.™ó|v.báþ¢)f†�Í]{+CH&šè¢‰.šè¢‰.zh¢‰.šŠº„¿—ß 3<¸¿hŠÙ…!¤F³VÜ�Lž~šBž˜œ½FCQtÑDMtÑD=4ÑDMaûÑ{ö‡ öl�®eÛ²K÷M1»0„ÔȆ�k¿õQ7„,¹ö} !&šè¢‰.šè¢‰.zh¢‰.š|AÎïzÖ� öâ¨sÖîpçcxpѳ CH�BŠq0ÑDMtÑDMtÑCMtÑd]ö:ÒoéyáÍìR î/šbva©CH1&šè¢‰.šè¢‰.zh¢‰.šV­Ûè~-®� 'M|2Ù°}ov †÷M1»0„ÔèÎåSJCÈÒ)Ù‹¥nž”]Ú¾8˜h¢‹&ºh¢‹&ºè¡‰&ºèÙ}àpò_¦<É"ˆû‹¦˜]Bj4}á†�L4ÑE]4ÑE]ôÐD]´lÛu0ùä´•n9õšeŒ b¸¿hŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&ºè°ä´ëžr#Èï^¹È½ -Ü_4ÅìÂR#B®¿á#nyú†_cÉp0ÑDMtÑDMtÑCMtÑŽ ößÅ«7d—@ ÷M1»0„ÔȆ�)S>ì†�g¾™½X*CQtÑDMtÑD=4ÑD—Ꭰö©;ö¼MQtѳ CH�BŠq0ÑDMtÑDMtÑCMt^ùÄ^(ÕÐE]4ÅìÂR#†�bL4ÑE]4ÑE]ôÐD]†O¹ÄÐE]4ÅìÂR#†�bL4ÑE]4ÑE]ôÐD]†G¥ÄÐE]4ÅìÂR#†�bL4ÑE]4ÑE]ôÐD]âl1tÑDM1»0„Ôˆ!¤MtÑDMtÑD=4ÑD—¸ªA ]4ÑESÌ. !5b)ÆÁD]4ÑE]4ÑEM4Ñ%žjGCMtѳ CH�BŠq0ÑDMtÑDMtÑCMt‰£–ÄÐE]4ÅìÂR#†�bL4ÑE]4ÑE]ôÐD]†^­#ˆ¡‹&ºhŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&º ­zFCMtѳ CH�BŠq0ÑDMtÑDMtÑCMt:áòÉi+«A ]4ÑESÌ. !5b)ÆÁD]4ÑE]4ÑEM4Ñeh„#ˆý×Þ®]4ÑESÌ. !5ê7„Lˆ�-Wg—¶/&šè¢‰.šè¢‰.zh¢‰.ÍgÏü°g€Ô;‚ºh¢‹¦˜]Bj4í±* !Ï~çWÓ!d[Wviûâ`¢‰.šè¢‰.šè¢‡&šèÒ\6‚Øk�42‚ºh¢‹¦˜]Bj4yþ… !8˜h¢‹&ºh¢‹&ºè¡‰&º4O³FCMtѳ CH�BŠq0ÑDMtÑDMtÑCMtiŽfŽ †.šè¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šèÒ¸p9iâ“Éæ�û³KêGMtѳ CH�lùæ5'º!ä…¿Æ’á`¢‰.šè¢‰.šè¢‡&šèÒ˜CGÞIιyMiÙ°}ovIc袉.šbva©‘ !Ó®z¿B~Þõ+éòÆ#٥틃‰&ºh¢‹&ºh¢‹šh¢Kýl9¿ëÙ¦� †.šè¢)f†�õB~ðÞté¾ÏGMtÑDMtÑD=4ÑD—ú åb袉.šbva©ÑÄŸ^ÀR€ƒ‰&ºh¢‹&ºh¢‹šh¢Kí†z1tÑDM1»0„Ôè’û>ËR€ƒ‰&ºh¢‹&ºh¢‹šh¢KmÂäø±=É3¿Ø“]Ò\tÑDM1»0„Ôˆ!¤MtÑDMtÑD=4ÑD—Ú\pÇúÒÒó›ٹÍGMtѳ CH�BŠq0ÑDMtÑDMtÑCMt©Þ%sž�2‚ºh¢‹¦˜]Bj!/ÝõîtÙóLviûâ`¢‰.šè¢‰.šè¢‡&šèR�˜#ˆ¡‹&ºhŠÙ…!¤Fý†�»�K‡�ý›³KÛMtÑDMtÑD=4ÑD—ÁÅA ]4ÑESÌ. !5b)ÆÁD]4ÑE]4ÑEM4Ñ¥²qó^t#È{.[m1tÑDM1»0„Ôˆ!¤MtÑDMtÑD=4ÑD—ò¦=þ’AŽ»ô‰döêײsã ‹&ºhŠÙ…!¤F !Å8˜h¢‹&ºh¢‹&ºè¡‰&ºÎÄÐE]4ÅìÂR#†�bL4ÑE]4ÑE]ôÐD]š¹üU7‚Ø©kÙ¶ìܸ袉.šbva©CH1&šè¢‰.šè¢‰.zh¢‰.ýÙ³?ìY 6‚سB† ]4ÑESÌ. !5 ‡�—gÿ›t9¼;»´}q0ÑDMtÑDMtÑCMt9Æ^ Ua1tÑDM1»0„Ô(B^¹ïè—φp0EMtÑDMtÑCMtImؾ79aü"7‚\6÷…ìÜáCMtѳ CH�BŠq0ÑDMtÑDMtÑCMtI’m»&§^³Ì� çw=›:òNvÉð¡‹&ºhŠÙ…!¤FáòÖOB<&šè¢‰.šè¢‰.zh¢©Ý»ì>p8ùä´•n9ëÆUîmÜ_4ÑESÌ. !5²!dú„÷º!d× !MtÑDMtÑD=4ÑÔÎ]ì™çݶÎ� öŒ{fˆ î/šè¢)f†�1„ã`¢‰.šè¢‰.šè¢‡&šÚ¹Ë×îÛäF�',v¯¢„û‹&ºhŠÙ¥é�â;;;“Q£F¹SWWWvn¥ëØŸkÅ¢‡ƒ‰&ºh¢‹&ºh¢‹šhj×.ßYø²Aì·ÄØo‹QÃýE]4ÅìÒÔGñ===ÉèÑ£³·7tôööfo¥º»»û�v}{¿Z0„èá`¢‰.šè¢‰.šè¢‡&šÚ±Ë¼u¯—~MîÌå¯fçjáþ¢‰.šbviê£x{6Hø,{Û†���ù!$?– FeyûQ†�ƒ‰&ºh¢‹&ºh¢‹šhj·.«^Þ�?¶Ç� lÎÎÕÃýE]4ÅìÒô!$>l)úñ˜ðÇgj}6ˆQBÜÂâp0ÑDMtÑDMtÑCMíÔÅ^ õ¤‰Oºä‚;Ögçjâþ¢‰.šbv–!ÄžbçÛ3CÂ¥©Cˆ&šè¢‰.šè¢‰.zh¢©�ºœ=}µAì×åªüšÜr¸¿h¢‹¦˜]š>„„ÃGÑ�ÆäÇ‘üûTcÕªUn ŽÓ7rYrã7~'™ÝñádÏ}§$}�ŸQx=Nœ8qâĉ'Nœ85ÿô©o<žüÎ�'÷=X|9'NœFæiýúxÏðjêb£Ç`/–jÇ�<{VH~, Mÿ¹6üëø�Ã^ÄÞöüåv _8`(ñ m0„€¶ÁÚCh ! m0„€¶ÁÚCh#jéîîNF�åNÙ¹ˆ¡§§§ôµÏý;;;Kçwuueç–?�ëíí-üºŽ=ºô5·f^­ç£>þ~-í¾â¿Æáe¾¡?yåÎG}Êý»aüùv?ðj=õ)ú÷!ÿ½~�í:þ|{_¯Üù¨O¹Âã˜Ý§¼ZÏG}ü×ÒNv?1á÷¾�Âÿ=P®c¹óQ?ÿ½îÕúo;ÿæ û^ÿ ¿÷íäïGü›?ôòßãá׳ÖÛ›ýoþˆBì/í¿qíÀÃ?®ñØÁ¾æyv~ø í•;�ó_[;„ÿÃÇþì á׿ÖóQÿõ´û‰}=½üÛ^x [”;õ �[ö=î¿¶ögßžÆöµ6µž�ÚÙ¿þûÚÿ$û¯�ŠŽCþ:žoQî|ÔÇîþþaÿõ÷�ðÏá×¼ÖóQ;ÖøïëðßûsÑq(¼Ž½Ÿ¿O•;õ³¯£}MÃïqûÞ÷÷£ðk^ëù¨Ÿ}=íë~�ÛŸíx”þ»aïc L¹óQ;ûºoòÿ>ø¯y­ç7bÄ !á?¬Æ“#ûF ¿þ^þà`oû6Eç£yìë~�ó{Û|j9�±ûIøuÍ¿mòsߣÜùh²á×ÕÞö�j9Íá¿÷Ë}¿ÛqÎÚyþØWî|4.üÚÚ÷zøï·¿OÔz>gMü÷x¹ïw»…_oÿ*w>êç�WþßmûzÖòo{¹óÑÿµ ¿–þ¼�ÝÂëøãW¹óQŸrß×á¿3ÆÓj=¿#j ÿò|SÆe_o;Xû“ÿƳ&áÿà ¿Q‹ÎGó俦ùƒ|x0¯å|4&ÿu´·ÃûŽ)úGÁ.+w>šÃ�­�uñü×½ÖóÑ8ûÚú¯¥}]ýýÄNþßüü±Îÿï�rç£~Ö¾öá÷wþ˜æÿ}¯õ|ÔÏß'Âã�}ïûóíä¿æÖÎîKžïQî|Ô'<þØ×ßýÛ`—Õz>êç�7ù¯­ýÙ¾¶vòçÛ÷xŸòïSî|ÔǾ~þko'ÿït¹Ãk=¿ !¨™ÿ†¶ÿZkãùoÒrç£yò_S;H[o°ÿñSî|4¦Ò×Ñîv²¯{þUŸ*:�³¯»¿¿äÿýð_÷ZÏGãü×·ˆ}ïÛeùcÝPþ�"¤ìké¿ÇóÇ4ûÛ嵞�ÆÙ÷{x,òÂ^ö_;Fy¾G¹óQ»ü¿þßé¢*ýÛ^î|ÔǾŸýý£èkëÙuì¾^ßø÷)w>š£ÞÛË�ß~4u±öMì¿1={;ü†õüùhžüÁ¼={;<¨{ƒ��ÆøûF;ß.·¯sø?v|�rç£1v? ÿ½°á×Õw©õ|4ƾ†•þ]ðÿnäûùc_¹óÑþ˜”ïäﵞ�æ(÷@ÙŸoݯ·ïXî|Ôξ¿íë�?ýìg?sÿõü×Ýþ[Ëù¨�ÿ}‹ð”gýìß»?„_oü*w>š£ÞÛË�߈3„øƒ…ý×äÿ¡E<á�Û„ ߨÜùhžüÀþÄk=�©ô?úíëí›Ù×ÛÃì<ߢÜù¨�} ‹þŒ“|§°K­ç£>Öd°¯¡ÿwþîögÏî#v^¹óQû¾öÿN‡_Ûð>þû_ëù¨OxüÊÿo+ÏÎ÷׳Žv2ÖÑ_¿Üùh\þ8TË¿íåÎGcìØSî{Üî+þkníü¿öµ·¦Üùhœ}m­OøïŒ±^v^­ç7bÄ !ƾ í `'q…_{;…ßxv@ñçû‹)w>c_Kÿuõ'/<Ï2^­ç£vvL ¿žþ”�güÝNá?ÖåÎGíì{Ú-ýÉ7ïGa—ZÏG튎aöoLþ|{Û ï_áÿ -w>j{ìþ›`Ç"~øï­ç£vùû…gÇ¡¢óMx>ÿæ=ûzzåþ ¯õ|4ƾ¿ý×3ÿ¿ÂÇ�åþm/w>j×ÌÛ›ýoþˆBÁÚCh ! m0„€¶ÁÚCh ! m0„€¶ÁÚChIòÿž¶{SØÿ\ IEND®B`‚sogou-srpc-ac65014/docs/installation.md000066400000000000000000000071661525217424400201330ustar00rootroot00000000000000# 安装 ## 1. Linuxæº�ç �安装 æº�ç �安装需è¦�一些å‰�ç½®ä¾�赖:CMake(è¦�求3.6以上)ã€�OpenSSL(推è��1.1å�Šä»¥ä¸Š)ã€�Protobuf(è¦�求3.5å�Šä»¥ä¸Šï¼‰ 默认会编译出: 1. é�™æ€�库:libsrpc.a(或者dylib) 2. 动æ€�库:libsrpc.so(或者dll) 3. 用于生æˆ�代ç �的二进制工具:srpc_generator - **cmake** ~~~sh git clone --recursive https://github.com/sogou/srpc.git cd srpc make make install # 编译示例 cd tutorial make ~~~ - **bazel(二选一)** ~~~sh git clone --recursive https://github.com/sogou/srpc.git cd srpc bazel build ... # 在bazel-bin/目录下,编译出libå’Œsrpc_generator以å�Šæ‰€æœ‰ç¤ºä¾‹ç¼–译出æ�¥çš„å�¯æ‰§è¡Œæ–‡ä»¶ ~~~ 此外,还å�¯ä»¥å€ŸåŠ©srpc_tools安装和部署脚手架。å°�工具用法å�‚考:[srpc/tools/README_cn.md](srpc/tools/README_cn.md) Workflowã€�snappyå’Œlz4也å�¯ä»¥ç³»ç»Ÿé¢„装,如果third_party中没有通过`--recursive`å�‚数拉å�–æº�ç �ä¾�赖,则会从系统默认安装路径寻找,snappy的预装è¦�求版本是v1.1.6或以上。 如果需è¦�æº�ç �安装`Protobuf`,å�‚考命令: ~~~sh git clone -b 3.20.x https://github.com/protocolbuffers/protobuf.git protobuf.3.20 cd protobuf.3.20 sh autogen.sh ./configure make make install ~~~ ## 2. Debian Linuxå’ŒUbuntu自带安装包 SRPCå·²ç»�打包到Debian,目å‰�是Debian sid(unstable)的自带安装包,最终会进入stableçš„repository。 需è¦�编辑/etc/apt/sources.list文件,æ‰�能访问unstableçš„repository: sources.list文件格å¼�: `deb ` å°†'unstable'字段加到我们的repo中,作为sub branch之一: ~~~~sh deb http://deb.debian.org/ main contrib non-free --> deb http://deb.debian.org/ unstable main contrib non-free ~~~~ ç„¶å�Župdate我们的repo列表,就å�¯ä»¥å®‰è£…SRPC: ~~~~sh sudo apt-get update ~~~~ 安装SRPC库用于开å�‘: ~~~~sh sudo apt-get install libsrpc-dev ~~~~ 安装SRPC库用于部署: ~~~~sh sudo apt-get install libsrpc ~~~~ ## 3. Fedora Linux自带安装包 SRPCå·²ç»�是Fedora系统的自带安装包。 为了开å�‘目的安装srpc库: ~~~sh sudo dnf install srpc-devel ~~~ è¦�安装srpc库以进行部署,请执行以下æ“�作: ~~~sh sudo dnf install srpc ~~~ ## 4. Windows下安装 Windows版下srpc代ç �无差异,注æ„�需è¦�ä¾�èµ–Workflowçš„[windows分支](https://github.com/sogou/workflow/tree/windows)。 å�¦å¤–,srpc_tools暂时ä¸�支æŒ�Windows下使用。 ## 5. MacOSæº�ç �安装 - 安装ä¾�èµ– `OpenSSL` ``` brew install openssl ``` - 安装 `CMake` ``` brew install cmake ``` - 指定 `OpenSSL` 环境å�˜é‡� 由于MacOS下默认有LibreSSL,因此在brew安装å�Žï¼Œå¹¶ä¸�会自动建软链,我们需è¦�手动把执行路径ã€�编译路径ã€�cmakeæ—¶çš„find_package路径都é…�置到bash的环境å�˜é‡�中。用户å�¯ä»¥æ‰§è¡Œ`brew info openssl`查看相关信æ�¯ï¼Œä¹Ÿå�¯ä»¥å¦‚下é…�置: ``` echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.bash_profile echo 'export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"' >> ~/.bash_profile echo 'export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"' >> ~/.bash_profile echo 'export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"' >> ~/.bash_profile echo 'export OPENSSL_ROOT_DIR=/usr/local/opt/openssl' >> ~/.bash_profile echo 'export OPENSSL_LIBRARIES=/usr/local/opt/openssl/lib' >> ~/.bash_profile ``` 如果使用zsh,则还需è¦�以下一步,把bashçš„é…�置加载一下: ``` echo 'test -f ~/.bash_profile && source ~/.bash_profile' >> ~/.zshrc source ~/.zshrc ``` 剩下的步骤和Linux下编译没有区别。 sogou-srpc-ac65014/docs/rpc.md000066400000000000000000000532271525217424400162150ustar00rootroot00000000000000[English version](/docs/en/rpc.md) ## 基础功能对比 |RPC |IDL |通信 | 网络数æ�® |压缩 | Attachement | å�Šå�Œæ­¥ | 异步 | Streaming | |---------------------------|-----------|------|------------|--------------------|------------|---------|--------|-------------| |Thrift Binary Framed | Thrift | tcp | 二进制 |ä¸�支æŒ� | ä¸�支æŒ� | 支æŒ� | ä¸�支æŒ� | ä¸�支æŒ� | |Thrift Binary HttpTransport| Thrift | http | 二进制 |ä¸�支æŒ� | ä¸�支æŒ� | 支æŒ� | ä¸�支æŒ� | ä¸�支æŒ� | |GRPC | PB | http2| 二进制 |gzip/zlib/lz4/snappy| 支æŒ� | ä¸�支æŒ� | 支æŒ� | 支æŒ� | |BRPC Std | PB | tcp | 二进制 |gzip/zlib/lz4/snappy| 支æŒ� | ä¸�支æŒ� | 支æŒ� | 支æŒ� | |SRPC Std | PB/Thrift | tcp | 二进制/JSON |gzip/zlib/lz4/snappy| 支æŒ� | 支æŒ� | 支æŒ� | ä¸�支æŒ� | |SRPC Std Http | PB/Thrift | http | 二进制/JSON |gzip/zlib/lz4/snappy| 支æŒ� | 支æŒ� | 支æŒ� | ä¸�支æŒ� | ## 基础概念 - 通信层:TCP/TPC_SSL/HTTP/HTTPS/HTTP2 - å��议层:Thrift-binary/BRPC-std/SRPC-std/SRPC-http/tRPC-std/tRPC-http - 压缩层:ä¸�压缩/gzip/zlib/lz4/snappy - æ•°æ�®å±‚:PB binary/Thrift binary/Json string - IDLåº�列化层:PB/Thrift serialization - RPC调用层:Service/Client IMPL ## RPC Global - 获å�–srpc版本å�·``srpc::SRPCGlobal::get_instance()->get_srpc_version()`` ## RPC Status Code |name | value |å�«ä¹‰ | |-----------------------------------|-----------|-------------------| |RPCStatusUndefined | 0 | 未定义 | |RPCStatusOK | 1 | 正确/æˆ�功 | |RPCStatusServiceNotFound | 2 | 找ä¸�到Serviceå�� | |RPCStatusMethodNotFound | 3 | 找ä¸�到RPC函数å�� | |RPCStatusMetaError | 4 | Meta错误/è§£æž�失败 | |RPCStatusReqCompressSizeInvalid | 5 | 请求压缩大å°�错误 | |RPCStatusReqDecompressSizeInvalid | 6 | 请求解压大å°�错误 | |RPCStatusReqCompressNotSupported | 7 | 请求压缩类型ä¸�支æŒ� | |RPCStatusReqDecompressNotSupported | 8 | 请求解压类型ä¸�支æŒ� | |RPCStatusReqCompressError | 9 | 请求压缩失败 | |RPCStatusReqDecompressError | 10 | 请求解压失败 | |RPCStatusReqSerializeError | 11 | 请求IDLåº�列化失败 | |RPCStatusReqDeserializeError | 12 | 请求IDLå��åº�列化失败| |RPCStatusRespCompressSizeInvalid | 13 | 回å¤�压缩大å°�错误 | |RPCStatusRespDecompressSizeInvalid | 14 | 回å¤�解压大å°�错误 | |RPCStatusRespCompressNotSupported | 15 | 回å¤�压缩类型ä¸�支æŒ� | |RPCStatusRespDecompressNotSupported| 16 | 回å¤�解压类型ä¸�支æŒ� | |RPCStatusRespCompressError | 17 | 回å¤�压缩失败 | |RPCStatusRespDecompressError | 18 | 回å¤�解压失败 | |RPCStatusRespSerializeError | 19 | 回å¤�IDLåº�列化失败 | |RPCStatusRespDeserializeError | 20 | 回å¤�IDLå��åº�列化失败| |RPCStatusIDLSerializeNotSupported | 21 | ä¸�支æŒ�IDLåº�列化 | |RPCStatusIDLDeserializeNotSupported| 22 | ä¸�支æŒ�IDLå��åº�列化 | |RPCStatusURIInvalid | 30 | URIé�žæ³• | |RPCStatusUpstreamFailed | 31 | Upstream全熔断 | |RPCStatusSystemError | 100 | 系统错误 | |RPCStatusSSLError | 101 | SSL错误 | |RPCStatusDNSError | 102 | DNS错误 | |RPCStatusProcessTerminated | 103 | 程åº�退出&终止 | ## RPC IDL - æ��述文件 - å‰�å�Žå…¼å®¹ - Protobuf/Thrift ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 我们拿pb举例,定义一个ServiceName为``Example``çš„``example.proto``文件 - rpc接å�£å��为``Echo``,输入å�‚数为``EchoRequest``,输出å�‚数为``EchoResponse`` - ``EchoRequest``包括两个string:``message``å’Œ``name`` - ``EchoResponse``包括一个string:``message`` ~~~proto syntax="proto2"; message EchoRequest { optional string message = 1; optional string name = 2; }; message EchoResponse { optional string message = 1; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; ~~~ ## RPC Service - 组æˆ�SRPCæœ�务的基本å�•å…ƒ - æ¯�一个Service一定由æŸ�一ç§�IDL生æˆ� - Serviceç”±IDL决定,与网络通信具体å��议无关 ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„``example.proto``IDLæ��述文件 - 执行官方的``protoc example.proto --cpp_out=./ --proto_path=./``获得``example.pb.h``å’Œ``example.pb.cpp``两个文件 - 执行SRPCçš„``srpc_generator protobuf ./example.proto ./``获得``example.srpc.h``文件 - 我们派生``Example::Service``æ�¥å®žçŽ°å…·ä½“çš„rpc业务逻辑,这就是一个RPC Service - 注æ„�这个Service没有任何网络ã€�端å�£ã€�通信å��议等概念,仅仅负责完æˆ�实现从``EchoRequest``输入到输出``EchoResponse``的业务逻辑 ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { response->set_message("Hi, " + request->name()); printf("get_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); } }; ~~~ ## RPC Server - æ¯�一个Server对应一个端å�£ - æ¯�一个Server对应一个确定的网络通信å��è®® - 一个Serviceå�¯ä»¥æ·»åŠ åˆ°ä»»æ„�çš„Server里 - 一个Serverå�¯ä»¥æ‹¥æœ‰ä»»æ„�多个Service,但在当å‰�Server里ServiceName必须唯一 - ä¸�å�ŒIDLçš„Service是å�¯ä»¥æ”¾è¿›å�Œä¸€ä¸ªServer中的 ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„``ExampleServiceImpl``Service - 首先,我们创建1个RPC Server,并确定proto文件的内容 - ç„¶å�Žï¼Œæˆ‘们å�¯ä»¥åˆ›å»ºä»»æ„�个数的Service实例ã€�ä»»æ„�ä¸�å�ŒIDL protoå½¢æˆ�çš„Service,把这些Service通过``add_service()``接å�£æ·»åŠ åˆ°Server里 - 最å�Žï¼Œé€šè¿‡Serverçš„``start()``或者``serve()``å¼€å�¯æœ�务,处ç�†å�³å°†åˆ°æ�¥çš„rpc请求 - 想åƒ�一下,我们也å�¯ä»¥ä»Ž``Example::Service``派生出多个Service,而它们的rpc``Echo``实现的功能å�¯ä»¥ä¸�å�Œ - 想åƒ�一下,我们å�¯ä»¥åœ¨N个ä¸�å�Œçš„端å�£åˆ›å»ºN个ä¸�å�Œçš„RPC Server,代表ç�€ä¸�å�Œçš„å��è®® - 想åƒ�一下,我们å�¯ä»¥æŠŠå�Œä¸€ä¸ªServiceIMPL实例``add_service()``到ä¸�å�Œçš„Server上,我们也å�¯ä»¥æŠŠä¸�å�Œçš„ServiceIMPL实例``add_service``到å�Œä¸€ä¸ªServer上 - 想åƒ�一下,我们å�¯ä»¥ç”¨å�Œä¸€ä¸ª``ExampleServiceImpl``,在三个ä¸�å�Œç«¯å�£ã€�å�Œæ—¶æœ�务于BRPC-STDã€�SRPC-STDã€�SRPC-Http - 甚至,我们å�¯ä»¥å°†1个Protobuf IDL相关的``ExampleServiceImpl``å’Œ1个Thrift IDL相关的``AnotherThriftServiceImpl``,``add_service``到å�Œä¸€ä¸ªSRPC-STD Server,两ç§�IDL在å�Œä¸€ä¸ªç«¯å�£ä¸Šå®Œç¾Žå·¥ä½œï¼� ~~~cpp int main() { SRPCServer server_srpc; SRPCHttpServer server_srpc_http; BRPCServer server_brpc; ThriftServer server_thrift; TRPCServer server_trpc; TRPCHttpServer server_trpc_http; ExampleServiceImpl impl_pb; AnotherThriftServiceImpl impl_thrift; server_srpc.add_service(&impl_pb); server_srpc.add_service(&impl_thrift); server_srpc_http.add_service(&impl_pb); server_srpc_http.add_service(&impl_thrift); server_brpc.add_service(&impl_pb); server_thrift.add_service(&impl_thrift); server_trpc.add_service(&impl_pb); server_trpc_http.add_service(&impl_pb); server_srpc.start(1412); server_srpc_http.start(8811); server_brpc.start(2020); server_thrift.start(9090); server_trpc.start(2022); server_trpc_http.start(8822); getchar(); server_trpc_http.stop(); server_trpc.stop(); server_thrift.stop(); server_brpc.stop(); server_srpc_http.stop(); server_srpc.stop(); return 0; } ~~~ ## RPC Client - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„目标/一个确定的集群 - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„网络通信å��è®® - æ¯�一个Client对应ç�€ä¸€ä¸ªç¡®å®šçš„IDL ### 示例 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 沿用上é�¢çš„例å­�,client相对简å�•,直接调用å�³å�¯ - 通过``Example::XXXClient``创建æŸ�ç§�RPCçš„client实例,需è¦�目标的ip+port或url - 利用client实例直接调用rpc函数``Echo``å�³å�¯ï¼Œè¿™æ˜¯ä¸€æ¬¡å¼‚步请求,请求完æˆ�å�Žä¼šè¿›å…¥å›žè°ƒå‡½æ•° - 具体的RPC Context用法请看下一个段è�½: [RPC Context](/docs/rpc.md#rpc-context)) ~~~cpp #include #include "example.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello!"); req.set_name("SRPCClient"); WFFacilities::WaitGroup wait_group(1); client.Echo(&req, [&wait_group](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } ~~~ ## RPC Context - RPCContext专门用æ�¥è¾…助异步接å�£ï¼ŒServiceå’ŒClient通用 - æ¯�一个异步接å�£éƒ½ä¼šæ��ä¾›Context,用æ�¥ç»™ç”¨æˆ·æ��供更高级的功能,比如获å�–对方ipã€�获å�–连接seqidç­‰ - Context上一些功能是Server或Client独有的,比如Serverå�¯ä»¥è®¾ç½®å›žå¤�æ•°æ�®çš„压缩方å¼�,Clientå�¯ä»¥èŽ·å�–请求æˆ�功或失败 - Context上å�¯ä»¥é€šè¿‡``get_series()``获得所在的series,与workflow的异步模å¼�æ— ç¼�结å�ˆ ### RPCContext API - Common #### ``long long get_seqid() const;`` 请求+回å¤�视为1次完整通信,获得当å‰�socket连接上的通信sequence id,seqid=0代表第1次 #### ``std::string get_remote_ip() const;`` 获得对方IP地å�€ï¼Œæ”¯æŒ�ipv4/ipv6 #### ``int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const;`` 获得对方地å�€ï¼Œin/outå�‚数为更底层的数æ�®ç»“æž„sockaddr #### ``const std::string& get_service_name() const;`` 获å�–RPC Service Name #### ``const std::string& get_method_name() const;`` 获å�–RPC Methode Name #### ``SeriesWork *get_series() const;`` 获å�–当å‰�ServerTask/ClientTask所在series #### ``bool get_http_header(const std::string& name, std::string& value);`` 如果通讯使用HTTPå��议,则根æ�®name获å�–HTTP header中的value ### RPCContext API - Only for client done #### ``bool success() const;`` client专用。这次请求是å�¦æˆ�功 #### ``int get_status_code() const;`` client专用。这次请求的rpc status code #### ``const char *get_errmsg() const;`` client专用。这次请求的错误信æ�¯ #### ``int get_error() const;`` client专用。这次请求的错误ç � #### ``void *get_user_data() const;`` client专用。获å�–ClientTaskçš„user_data。如果用户通过``create_xxx_task()``接å�£äº§ç”Ÿtask,则å�¯ä»¥é€šè¿‡user_data域记录上下文,在创建task时设置,在回调函数中拿回。 ### RPCContext API - Only for server process #### ``void set_data_type(RPCDataType type);`` Server专用。设置数æ�®æ‰“包类型 - RPCDataProtobuf - RPCDataThrift - RPCDataJson #### ``void set_compress_type(RPCCompressType type);`` Server专用。设置数æ�®åŽ‹ç¼©ç±»åž‹(注:Client的压缩类型在Client或Task上设置) - RPCCompressNone - RPCCompressSnappy - RPCCompressGzip - RPCCompressZlib - RPCCompressLz4 #### ``void set_attachment_nocopy(const char *attachment, size_t len);`` Server专用。设置attachment附件。 #### ``bool get_attachment(const char **attachment, size_t *len) const;`` Server专用。获å�–attachment附件。 #### ``void set_reply_callback(std::function cb);`` Server专用。设置reply callback,æ“�作系统写入socket缓冲区æˆ�功å�Žè¢«è°ƒç”¨ã€‚ #### ``void set_send_timeout(int timeout);`` Server专用。设置å�‘é€�超时,å�•ä½�毫秒。-1代表无é™�。 #### ``void set_keep_alive(int timeout);`` Server专用。设置连接ä¿�活时间,å�•ä½�毫秒。-1代表无é™�。 #### ``bool set_http_code(int code);`` Server专用。如果通讯使用HTTPå��议,则å�¯ä»¥è®¾ç½®http status code返回ç �。仅在框架层能正确å“�应时有效。 #### ``bool set_http_header(const std::string& name, const std::string& value);`` Server专用。如果通讯使用HTTPå��议,å�¯ä»¥åœ¨å›žå¤�中设置HTTP header,如果name被设置过会覆盖旧value。 #### ``bool add_http_header(const std::string& name, const std::string& value);`` Server专用。如果通讯使用HTTPå��议,å�¯ä»¥åœ¨å›žå¤�中添加HTTP header,如果有é‡�å¤�name,会ä¿�留多个value。 #### ``void log(const RPCLogVector& fields);`` Server专用。é€�ä¼ æ•°æ�®ç›¸å…³ï¼Œè¯·å�‚考OpenTelemetryæ•°æ�®å��议中的log语义。 #### ``void baggage(const std::string& key, const std::string& value);`` Server专用。é€�ä¼ æ•°æ�®ç›¸å…³ï¼Œå�‚考OpenTelemetryæ•°æ�®å��议中的baggage语义。 #### ``void set_json_add_whitespace(bool on);`` Server专用。JsonPrintOptions相关,å�¯è®¾ç½®å¢žåŠ json空格等。 #### ``void set_json_always_print_enums_as_ints(bool flag);`` Server专用。JsonPrintOptions相关,å�¯è®¾ç½®ç”¨int打å�°enumå��。 #### ``void set_json_preserve_proto_field_names(bool flag);`` Server专用。JsonPrintOptions相关,å�¯è®¾ç½®ä¿�留原始字段å��字。 #### ``void set_json_always_print_fields_with_no_presence(bool flag);`` Server专用。JsonPrintOptions相关,å�¯è®¾ç½®å¸¦ä¸Šæ‰€æœ‰é»˜è®¤çš„protoæ•°æ�®ä¸­çš„域。 ## RPC Options ### Server Params |name |默认 |å�«ä¹‰ | |---------------------------|--------------------------|--------------------------------| |max_connections | 2000 | Server的最大连接数,默认2000个 | |peer_response_timeout | 10 * 1000 | æ¯�一次IO的读超时,默认10ç§’ | |receive_timeout | -1 | æ¯�一æ�¡å®Œæ•´æ¶ˆæ�¯çš„读超时,默认无é™� | |keep_alive_timeout | 60 * 1000 | 空闲连接ä¿�活,-1代表永远ä¸�断开,0代表短连接,默认长连接ä¿�æ´»60ç§’ | |request_size_limit | 2LL * 1024 * 1024 * 1024 | 请求包大å°�é™�制,最大2GB | |ssl_accept_timeout | 10 * 1000 | SSL连接超时,默认10ç§’ | ### Client Params |name |默认 |å�«ä¹‰ | |---------------------------|--------------------------|--------------------------------| |host | "" | 目标host,å�¯ä»¥æ˜¯ipã€�域å�� | |port | 1412 | 目标端å�£å�·ï¼Œé»˜è®¤1412 | |is_ssl | false | ssl开关,默认关闭 | |url | "" | 当host为空,url设置æ‰�有效。urlå°†å±�蔽host/port/is_ssl三项 | |task_params | TASK默认é…�ç½® | è§�下方 | ### Task Params |name |默认 |å�«ä¹‰ | |---------------------------|--------------------------|--------------------------------| |send_timeout | -1 | å�‘é€�写超时,默认无é™� | |receive_timeout | -1 | 回å¤�超时,默认无é™� | |watch_timeout | 0 | 对方第一次回å¤�的超时,默认0ä¸�设置 | |keep_alive_timeout | 30 * 1000 | 空闲连接ä¿�活,-1代表永远ä¸�断开,默认30s | |retry_max | 0 | 最大é‡�试次数,默认0ä¸�é‡�试 | |compress_type | RPCCompressNone | 压缩类型,默认ä¸�压缩 | |data_type | RPCDataUndefined | 网络包数æ�®ç±»åž‹ï¼Œé»˜è®¤ä¸ŽRPC默认值一致,SRPC-Httpå��议为json,其余为对应IDL的类型 | ## 与workflow异步框架的结å�ˆ ### 1. Server 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - Echo RPC在接收到请求时,å�‘下游å�‘起一次http请求 - 对下游请求完æˆ�å�Žï¼Œæˆ‘们将http responseçš„bodyä¿¡æ�¯å¡«å……到responseçš„message里,回å¤�给客户端 - 我们ä¸�希望阻塞/å� æ�®ç�€Handler的线程,所以对下游的请求一定是一次异步请求 - 首先,我们通过Workflow框架的工厂``WFTaskFactory::create_http_task``创建一个异步任务http_task - ç„¶å�Žï¼Œæˆ‘们利用RPCContextçš„``ctx->get_series()``获å�–到ServerTask所在的SeriesWork - 最å�Žï¼Œæˆ‘们使用SeriesWorkçš„``push_back``接å�£å°†http_task放到SeriesWorkçš„å�Žé�¢ ~~~cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [request, response](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); response->mutable_message()->assign((const char *)data, len); } else response->set_message("Error: " + std::to_string(task->get_error())); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", request->DebugString().c_str(), response->DebugString().c_str()); }); ctx->get_series()->push_back(http_task); } }; ~~~ ### 2. Client 下é�¢æˆ‘们通过一个具体例å­�æ�¥å‘ˆçް - 我们并行å�‘出两个请求,1个是rpc请求,1个是http请求 - 两个请求都结æ�Ÿå�Žï¼Œæˆ‘们å†�å�‘起一次计算任务,计算两个数的平方和 - 首先,我们通过RPC Clientçš„``create_Echo_task``创建一个rpc异步请求的网络任务rpc_task - ç„¶å�Žï¼Œæˆ‘们通过Workflow框架的工厂``WFTaskFactory::create_http_task``å’Œ``WFTaskFactory::create_go_task``分别创建异步网络任务http_task,和异步计算任务calc_task - 最å�Žï¼Œæˆ‘们利用串并è�”æµ�程图,乘å�·ä»£è¡¨å¹¶è¡Œã€�大于å�·ä»£è¡¨ä¸²è¡Œï¼Œå°†3个异步任务组å�ˆèµ·æ�¥æ‰§è¡Œ``start()`` ~~~cpp void calc(int x, int y) { int z = x * x + y * y; printf("calc result: %d\n", z); } int main() { Example::SRPCClient client("127.0.0.1", 1412); auto *rpc_task = client.create_Echo_task([](EchoResponse *response, RPCContext *ctx) { if (ctx->success()) printf("%s\n", response->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { std::string body; const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); body.assign((const char *)data, len); printf("%s\n\n", body.c_str()); } else printf("Http request fail\n\n"); }); auto *calc_task = WFTaskFactory::create_go_task(calc, 3, 4); EchoRequest req; req.set_message("Hello!"); req.set_name("1412"); rpc_task->serialize_input(&req); WFFacilities::WaitGroup wait_group(1); SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) { wait_group.done(); }); series->push_back(rpc_task); series->push_back(calc_task); series->start(); wait_group.wait(); return 0; } ~~~ ### 3. Upstream SRPCå�¯ä»¥ç›´æŽ¥ä½¿ç”¨Workflow的任何组件,最常用的就是[Upstream](https://github.com/sogou/workflow/blob/master/docs/about-upstream.md),SRPC的任何一ç§�client都å�¯ä»¥ä½¿ç”¨Upstream。 我们通过å�‚æ•°æ�¥çœ‹çœ‹å¦‚何构造å�¯ä»¥ä½¿ç”¨Upstreamçš„client: ```cpp #include "workflow/UpstreamManager.h" int main() { // 1. 创建upstream并添加实例 UpstreamManager::upstream_create_weighted_random("echo_server", true); UpstreamManager::upstream_add_server("echo_server", "127.0.0.1:1412"); UpstreamManager::upstream_add_server("echo_server", "192.168.10.10"); UpstreamManager::upstream_add_server("echo_server", "internal.host.com"); // 2. 构造å�‚数,填上upstreamçš„å��å­— RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.host = "srpc::echo_server"; // 这个schemeå�ªç”¨äºŽupstream URIè§£æž� client_params.port = 1412; // 这个portå�ªç”¨äºŽupstream URIè§£æž�,ä¸�å½±å“�具体实例的选å�– // 3. 用å�‚数创建client,其他用法与示例类似 Example::SRPCClient client(&client_params); ... ``` 如果使用了ConsistentHash或者Manualæ–¹å¼�创建upstream,则我们往往需è¦�对ä¸�å�Œçš„task进行区分ã€�以供选å�–算法使用。这时候å�¯ä»¥ä½¿ç”¨client task上的`int set_uri_fragment(const std::string& fragment);`接å�£ï¼Œè®¾ç½®è¯·æ±‚级相关的信æ�¯ã€‚ 这个域的是URI里的fragment,语义请å�‚考[RFC3689 3.5-Fragment](https://datatracker.ietf.org/doc/html/rfc3986#section-3.5),任何需è¦�用到fragment的功能(如其他选å�–策略里附带的其他信æ�¯ï¼‰ï¼Œéƒ½å�¯ä»¥åˆ©ç”¨è¿™ä¸ªåŸŸã€‚ sogou-srpc-ac65014/docs/wiki.md000066400000000000000000000477601525217424400164010ustar00rootroot00000000000000# SRPC架构介ç»� - Sogou基于Workflow的自研RPC框架 srpc是sogou内部自研的基于Workflow项目å�šçš„RPC生æ€�项目,通过解æž�部分IDL(接å�£æ��述文件)和进行代ç �生æˆ�,实现了与workflow底层通信框架的对接和é�žå¸¸ç®€æ´�的用户接å�£ã€‚ srpc整体代ç �é‡�大约1万行,内部使用多ç§�泛型编程的方å¼�,实现纵å�‘解耦å�ˆã€�横å�‘å�¯æ‰©å±•,核心使用å�Œä¸€å¥—代ç �,架构å��分精巧美丽。以下是架构设计æ€�路详解。 ## 目录 **一ã€�整体介ç»�** **二ã€�接å�£æ��è¿°ã€�å��议和通信层次介ç»�** 1. 接å�£æ��述文件层 2. RPCå��议层 3. 网络通信层 4. å��è®®+网络通信结å�ˆ **三ã€�架构层次介ç»�** **å››ã€�代ç �生æˆ�** 1. IDL原生接å�£ 2. srpc代ç �生æˆ�的接å�£ 3. srpc内部å�„网络å��è®® 4. service与serverçš„n对m关系 **五ã€�å�Œæ­¥ã€�异步ã€�å�Šå�Œæ­¥æŽ¥å�£ï¼Œå�Šæ‰“通workflow任务æµ�** 1. 异步接å�£ 2. å�Œæ­¥æŽ¥å�£ 3. å�Šå�Œæ­¥æŽ¥å�£ 4. task接å�£ 5. 利用Context打通workflow任务æµ�,以å�Šå¼‚æ­¥serverçš„å�𿳕 **å…­ã€�å…¶ä»–å�¯ä»¥å€Ÿç”¨workflowå�šçš„事情** **七ã€�写在最å�Ž** ## 一ã€�整体介ç»� 作为一个RPC,srpc的定ä½�很明确,是å�šå¤šä¸ªå±‚级的纵å�‘拆解和横å�‘解耦。层次包括: * **用户代ç �**(clientçš„å�‘é€�函数/server的函数实现) * **IDLåº�列化**(protobuf/thrift serialization) * **æ•°æ�®ç»„织** (protobuf/thrift/json) * **压缩**(none/gzip/zlib/snappy/lz4) * **å��è®®** (SRPC-std/BRPC-std/Thrift-framed/TRPC) * **通信** (TCP/HTTP) 以上å�„层级å�¯ä»¥ç›¸äº’拼装,利用函数é‡�è½½ã€�派生å­�类实现父类接å�£å’Œæ¨¡ç‰ˆç‰¹åŒ–等多ç§�多æ€�æ–¹å¼�,æ�¥å®žçŽ°å†…éƒ¨ä½¿ç”¨å�Œä¸€å¥—代ç �的高度å¤�用,以å�Žå¦‚果想架构å�‡çº§ï¼Œæ— è®ºæ˜¯ä¸­é—´å†�加一层ã€�还是æŸ�层内横å�‘添加一ç§�内容都é�žå¸¸æ–¹ä¾¿ï¼Œæ•´ä½“设计比较精巧。 且得益于workflow的性能,srpc本身的性能也很优异。srpc系统除了SRPC-stdå��议以外,å�Œæ—¶å®žçŽ°äº†BRPC-stdå��è®®ã€�Thrift-framedå��è®®ã€�腾讯的TRPCå��议,å�Œå��议下与brpc系统和apache thrift系统进行了性能对比,å�žå��性能领先,长尾性能与brpcå�„有优势但都比thrift强。 srpcç›®å‰�在æ�œç‹—å…¬å�¸å†…部和开æº�å�Žä¸šç•Œä¸€äº›å¼€å�‘者的团队中已ç»�稳定使用一段时间,并以其简å�•的接å�£è®©å°�伙伴们å�¯ä»¥å¿«é€Ÿå¼€å�‘,且å��助业务性能带æ�¥å¥½å‡ å€�çš„æ��å�‡ã€‚ 本篇主è¦�是对srpc架构设计æ€�路的é˜�述,建议开å�‘者结å�ˆä¸»é¡µreadme等文档一起å�‚考。 ## 二ã€� 接å�£æ��è¿°ã€�å��议和通信层次介ç»� 一般æ�¥è¯´ç”¨æˆ·ä½¿ç”¨ä»»ä½•一ç§�RPC,需è¦�关心的是:``method``,``request``,``response``,以下会结å�ˆè¿™ä¸‰é¡¹æ�¥è®²è¿°å¦‚何和å�„层级打交é�“。 刚æ‰�整体介ç»�里看到的层级很多,但是作为srpc的开å�‘,需è¦�知é�“的层级是以下三ç§�: srpc_protocol_transport_hierarchy ### 1. 接å�£æ��述文件层 如图从左到å�³ï¼Œæ˜¯ç”¨æˆ·æŽ¥è§¦å¾—最多到最少的层次。我们作为一个已ç»�使用了æŸ�ç§�RPC的用户,一般会选择一ç§�现有的IDL,目å‰�å°�伙伴们用得比较多的是``protobuf``å’Œ``thrift``,这两ç§�srpc都支æŒ�了。其中: * thrift:IDL纯手工解æž�,用户使用srpc是ä¸�需è¦�链thrift的库的 ï¼�ï¼�ï¼� * protobuf:service的定义部分纯手工解æž� 用户在写开å�‘代ç �的时候,必定是请求一个method,拿ç�€æŸ�ç§�IDLå¡«request,拿回å�Œç§�IDL定义的response。 **一ç§�IDL决定了一ç§�service。** ### 2. RPCå��议层 中间那列是具体的å��议,我们实现了``SRPC-std``,``BRPC-std``,``Thrift-framed``å’Œ``TRPC``,其中**SRPC-std**çš„å��议如下: srpc_protocol 其中RPCçš„``service``å��å­—å’Œ``method``å��字等信æ�¯ä¼šçš„填到``meta``中,第三部分是完完整整的用户的IDL:具体的``request``/``response``的内容。 ### 3. 网络通信层 以上一片内容,如果直接二进制走TCPå�‘出去,那么肯定è¦�求对方è¦�是相å�Œå��议的server/clientæ‰�能解æž�。但是,如果我们希望跨语言,最简å�•的方å¼�是借用HTTPå��议,于是乎,我们的第三列网络通信,和TCP二进制内容并列的,是HTTPå��议。 ### 4. å��è®®+网络通信结å�ˆ 第二列和第三列ç�†è®ºä¸Šæ˜¯å�¯ä»¥ä¸¤ä¸¤ç»“å�ˆçš„,å�ªéœ€è¦�第二列的具体RPCå��议在å�‘é€�时,把HTTP相关的内容进行特化,ä¸�è¦�按照自己的å��议去å�‘,而按照HTTP需è¦�的形å¼�去å�‘。HTTP需è¦�什么?**URL**,**header**å’Œ**body**。 我们把``service``/``method``拼到``URL``çš„hostå�Žé�¢ï¼ŒæŽ¥ä¸‹æ�¥æŠŠå…·ä½“å��议的``meta``的内容按照HTTPå�¯ä»¥è¯†åˆ«çš„header key一项一项填进``header``里。 最å�ŽæŠŠå…·ä½“``request``/``response``作为HTTPçš„``body``填好,å�‘é€�出去。 我们除了BRPC + Http没有实现,其他两两结å�ˆä¸€å…±å®žçŽ°å‡ºäº†7ç§�网络通信å��议: * SRPCStd * SRPCHttp * BRPCStd * TRPCStd * TRPCHttp * ThriftBinaryFramed * ThriftBinaryHttp å�¦å¤–,为了更好地兼容HTTP用户,我们的body除了``protobuf``/``thrift``之外,还å�¯ä»¥æ”¯æŒ�``json``,也就是说,clientå�¯ä»¥æ˜¯ä»»ä½•语言,å�ªè¦�å�‘一个带jsonçš„HTTP请求给server,server收到的ä¾�然是一个IDL结构体。 **一ç§�网络通信å��议决定了一ç§�server/client/task。** ## 三ã€�架构层次介ç»� 我们先看一下,把request通过methodå�‘é€�出去并处ç�†responseå†�回æ�¥çš„æ•´ä»¶äº‹æƒ…是怎么å�šçš„: srpc_one_round æ ¹æ�®ä¸Šå›¾ï¼Œ å�¯ä»¥æ›´æ¸…楚地看到文章开头æ��到的å�„个层级,其中压缩层ã€�åº�列化层ã€�å��议层是互相解耦打通的,代ç �实现上é�žå¸¸ç»Ÿä¸€ï¼Œæ¨ªå�‘增加任何一ç§�压缩算法或IDL或å��议都ä¸�需è¦�改动现有的代ç �~ 在å�šsrpc的架构设计期间的工作基本å�¯ä»¥æ€»ç»“为:ä¸�å�œåœ°è§£è€¦ã€‚ å�¦å¤–,作为一个通信框架,srpc为了支æŒ�外部æœ�务的调用和å�¯æ‰©å±•的控制逻辑,对å�„个切é�¢è¿›è¡Œäº†**模å�—化æ�’ä»¶**的设计。以server收消æ�¯åˆ°å›žå¤�的一æ�¡çº¿å±•开,模å�—化æ�’件的架构图如下: srpc_module æ�’件如果需è¦�使用,都æ��å‰�需è¦�注册,以å�šåˆ°ä¸�使用的模å�—无任何é¢�外消耗。æ¯�个模å�—的内部实现都会把具体执行方å¼�å°�装æˆ�一个任务,这是é�µå¾ªworkflow内部任务æµ�的编程范å¼�æ€�想。 ## å››ã€� 代ç �生æˆ� å�¯ä»¥çœ‹åˆ°ä¸Šå›¾ä¸­ï¼Œæœ‰è®¸å¤šæ­¥éª¤æ˜¯é€šè¿‡ä»£ç �生æˆ�完æˆ�的。å�šrpc框架,因为用户è¦�自定义自己的IDL结构体,对于网络上收到一片内存如何按照结构体定义的内容å��射出æ�¥ï¼Œå¹¶ä¸”为用户æ��供自定义结构体作为å�‚数的接å�£ï¼Œè¿™äº›å¯¹äºŽC++æ�¥è¯´éƒ½æ˜¯éœ€è¦�生æˆ�一部分代ç �æ�¥å�šçš„。ç�†è®ºä¸Šï¼Œå¦‚æžœä¾�赖了protobufå’Œthrift,确实å�¯ä»¥ä¸�用纯手工代ç �生æˆ�。 因此这里给大家介ç»�一下我们为什么è¦�代ç �生æˆ�。 ### 1. IDL原生接å�£ 我们说过,对于一个protobufçš„service定义,或者thrift的全部内容,我们都选择了代ç �生æˆ�。这里以protobuf为例: ```cpp message EchoRequest { optional string message = 1; }; message EchoResponse { optional string message = 1; }; service ExamplePB { rpc Echo(EchoRequest) returns (EchoResponse); }; ``` 其实pb本身å�ªéœ€è¦�é…�上: ```cpp option cc_generic_services = true; ``` å°±å�¯ä»¥å¸®ä½ ç”Ÿæˆ�具体service/method的接å�£äº†ï¼Œä½†æ˜¯protobuf本身ä¸�带网络通信功能,你需è¦�基于这个接å�£å�šå¼€å�‘,但是我们å�¯ä»¥çœ‹çœ‹pb这个原生接å�£ï¼Œé�žå¸¸çš„å¤�æ�‚: ```cpp // SERVER代ç � class ExamplePB : public ::PROTOBUF_NAMESPACE_ID::Service { public: // 这里是接å�£å±‚,用户需è¦�派生这个类,实现å�Œå��函数,在函数里写server的业务逻辑 virtual void Echo(::PROTOBUF_NAMESPACE_ID::RpcController* controller, const ::example::EchoRequest* request, ::example::EchoResponse* response, ::google::protobuf::Closure* done); }; // CLIENT代ç � class ExamplePB_Stub : public ExamplePB { public: // å�‘èµ·RPC调用也需è¦�知é�“controllerå’ŒClosure done void Echo(::PROTOBUF_NAMESPACE_ID::RpcController* controller, const ::example::EchoRequest* request, ::example::EchoResponse* response, ::google::protobuf::Closure* done); }; ``` google的风格就是喜欢把接å�£å±‚和实现层区分开æ�¥ï¼Œæƒ³è¦�èµ°protobuf默认的代ç �生æˆ�,就需è¦�让用户也这样写``controller``å’Œ``closure``,这两者都是å�¯ä»¥å†�å�šå…·ä½“实现的,一般æ�¥è¯´æ”¯æŒ�protobufçš„RPC都是这样å�šçš„。但是,用户è¦�看到这么多å¤�æ�‚的东西会很头疼,尤其是``controller``,明明有了client还è¦�多学习一个结构,ä¸�太符å�ˆæž¶æž„的审美。 å�¦å¤–一点,responseå’Œcontroller的生命周期也是一个很confusing的东西,pbè¿™ç§�接å�£æ˜¯éœ€è¦�让用户传入,用户æ�¥ä¿�è¯�生命周期必须ä¿�æŒ�到异步请求结æ�Ÿï¼Œæ˜¾ç„¶ä¸�太å�‹å¥½ã€‚ thriftçš„è¯�就更ä¸�用说了,请求与连接本身结å�ˆå¤ªç´§å¯†ï¼Œä¸�利于我们自己å�šç½‘络通信层。 ### 2. srpc代ç �生æˆ�的接å�£ 为了支æŒ�两ç§�ä¸�å�Œçš„IDL都å�¯ä»¥é¡ºåˆ©è¯·æ±‚workflow,我们进行了代ç �生æˆ�,包括基本的``generator``->``parser``->``printer``,这部分风格代ç �比较C。需è¦�编译完之å�Žå¯¹IDL文件进行如下调用: ```cpp srpc_generator [protobuf|thrift] [proto_file|thrift_file] out_dir ``` 具体文件生æˆ�到``xxx.srpc.h``中,还是拿刚æ‰�protobuf的例å­�,srpc生æˆ�出æ�¥çš„代ç �如下: ```cpp // SERVER代ç � class Service : public srpc::RPCService { public: // 用户需è¦�自行派生实现这个函数,与刚æ‰�pb生æˆ�的是对应的 virtual void Echo(EchoRequest *request, EchoResponse *response, srpc::RPCContext *ctx) = 0; }; // CLIENT代ç � using EchoDone = std::function; class SRPCClient : public srpc::SRPCClient { public: void Echo(const EchoRequest *req, EchoDone done); }; ``` 用户的接å�£ä½¿ç”¨èµ·æ�¥å°±ä¼šé�žå¸¸ç®€å�•,尤其是client的接å�£ï¼Œå…¶ä¸­``done``是我们的回调函数,而类似于controllerè¿™ç§�**会è¯�上下文信æ�¯**的东西,用户ä¸�想用的è¯�å�¯ä»¥é€‰æ‹©æ€§å¿½ç•¥ã€‚ 我们还模仿了thrift给生æˆ�了skeleton示例: ``server.xx_skeleton.cc``å’Œ``client.xx_skeleton.cc``,帮忙在main函数里构造了clientå’Œserver以å�Šservice的派生,妈妈å†�也ä¸�用担心我连创建的步骤都æ�žé”™äº†ã€‚ ```cpp //以下是自动生æˆ�的代ç �server.pb_skeleton.cc #include "xxxx.srpc.h" class ExamplePBServiceImpl : public ::example::ExamplePB::Service { public: void Echo(::example::EchoRequest *request, ::example::EchoResponse *response, srpc::RPCContext *ctx) override { /* 收到请求å�Žè¦�å�šçš„事情,并填好回å¤�;*/ } }; int main() { // 1. 定义一个server SRPCServer server; // 2. 定义一个service,并加到server中 ExamplePBServiceImpl examplepb_impl; server.add_service(&examplepb_impl); // 3. 把serverå�¯åŠ¨èµ·æ�¥ server.start(PORT); // 4. serverå�¯åŠ¨æ˜¯å¼‚æ­¥çš„ï¼Œéœ€è¦�暂时å�¡ä½�主线程 getchar(); server.stop(); return 0; } ``` ### 3. srpc内部å�„网络å��è®® 这里å�¯ä»¥é¡ºç�†æˆ�章地给梳ç�†é€šä¿¡å±‚å’Œå��议层的é…�å�ˆäº†ã€‚我们继续看看``xxxx.srpc.h``这个文件,里边定义了好几ç§�å��议: ```cpp // ç”±protobuf生æˆ�的代ç � class Service : public srpc::RPCService; class SRPCClient : public srpc::SRPCClient; class SRPCHttpClient : public srpc::SRPCHttpClient; class BRPCClient : public srpc::BRPCClient; class TRPCClient : public srpc::TRPCClient; ``` 如果我们的IDL是thrift,那么会生æˆ�çš„xxxx.srpc.h就会有这些: ```cpp // ç”±thrift生æˆ�的代ç � class Service : public srpc::RPCService; class SRPCClient : public srpc::SRPCClient; class SRPCHttpClient : public srpc::SRPCHttpClient; class ThriftClient : public srpc::ThriftClient; class ThriftHttpClient : public srpc::ThriftHttpClient; ``` å�¯ä»¥çœ‹åˆ°ï¼Œå¤§å®¶éƒ½æ˜¯å�ªæœ‰ä¸€ä¸ªservice,因为**serviceå�ªä¸ŽIDL相关**。 ç„¶å�Žå¯¹äºŽä¸�å�Œçš„RPCå��议(``SRPC``/``BRPC``/``Thrift``/``TRPC``),如果有é…�套的通信å��议(``TCP``/``HTTP``)实现,那么就会有一个对应的client。 ### 4. service与serverçš„N对M关系 我们刚æ‰�介ç»�过,**一个IDL定义一个servcie,一个网络通信å��议定义一个server**。然å�Žsrpc内部å�„层是å�¯ä»¥äº’相组å�ˆçš„,这æ„�味ç�€ï¼š * 一个å��议的server内å�¯ä»¥æ·»åŠ å¤šä¸ªservice(这个ä¸�新鲜,除了部分rpcä¸�å�¯ä»¥ï¼Œå¤§éƒ¨ä»½äººéƒ½æ˜¯å�¯ä»¥çš„) * 用户实现一个service的函数,**å�¯ä»¥åŠ åˆ°å¤šä¸ªä¸�å�Œå��议的server中**,都å�¯ä»¥runèµ·æ�¥ï¼› * 一个service的函数也å�¯ä»¥å�Œæ—¶æ”¯æŒ�æœ�务TCPå’ŒHTTP了,业务逻辑å¤�用é�žå¸¸æ–¹ä¾¿ï½ž ```cpp int main() { SRPCServer server_srpc; SRPCHttpServer server_srpc_http; BRPCServer server_brpc; ThriftServer server_thrift; TRPCServer server_trpc; TRPCHttpServer server_trpc_http; ExampleServiceImpl impl_pb; // 使用pb作为接å�£çš„service AnotherThriftServiceImpl impl_thrift; // 使用thrift作为接å�£çš„service server_srpc.add_service(&impl_pb); // å�ªè¦�å��议本身支æŒ�è¿™ç§�IDL,就å�¯ä»¥æŠŠè¿™ç±»service往里加 server_srpc.add_service(&impl_thrift); server_srpc_http.add_service(&impl_pb); // 还å�¯ä»¥å�Œæ—¶æ��供二进制å��议和Httpæœ�务 server_srpc_http.add_service(&impl_thrift); server_brpc.add_service(&impl_pb); // brpc-stdå��è®®å�ªæ”¯æŒ�了protobuf server_thrift.add_service(&impl_thrift); // thrift-binaryå��è®®å�ªæ”¯æŒ�了thrift server_trpc.add_service(&impl_pb); // å�ªéœ€è¦�改一个字æ¯�,就å�¯ä»¥æ–¹ä¾¿å…¼å®¹ä¸�å�Œå��è®® server_trpc_http.add_service(&impl_pb); // ç›®å‰�也是唯一开æº�çš„trpcå��议实现 server_srpc.start(1412); server_srpc_http.start(8811); server_brpc.start(2020); server_trpc.start(2021); server_thrift.start(9090); server_trpc_http.start(8822); .... return 0; } ``` ## 五ã€�å�Œæ­¥ã€�异步ã€�å�Šå�Œæ­¥æŽ¥å�£ï¼Œå�Šæ‰“通workflow任务æµ� srpcæ��供了å�Œæ­¥ã€�å�Šå�Œæ­¥ã€�异步的接å�£ï¼Œå…¶ä¸­å¼‚步接å�£æ˜¯ç”¨æ�¥æ‰“通workflow任务æµ�的关键。 如果我们继续看生æˆ�代ç �,其中一个client为例,一个具体å��议所定义出æ�¥çš„client所有的接å�£æ˜¯è¿™æ ·çš„: ```cpp class SRPCClient : public srpc::SRPCClient { public: // 异步接å�£ void Echo(const EchoRequest *req, EchoDone done); // å�Œæ­¥æŽ¥å�£ void Echo(const EchoRequest *req, EchoResponse *resp, srpc::RPCSyncContext *sync_ctx); // å�Šå�Œæ­¥æŽ¥å�£ WFFuture> async_Echo(const EchoRequest *req); public: SRPCClient(const char *host, unsigned short port); SRPCClient(const struct srpc::RPCClientParams *params); public: // 异步接å�£ï¼Œç”¨æ³•与workflowå…¶ä»–task一致 srpc::SRPCClientTask *create_Echo_task(EchoDone done); }; ``` 这几ç§�接å�£ï¼Œç›¸å½“于是å�˜ç�€èŠ±æ ·åœ°ä½¿ç”¨è¿™å››ç§�东西:``request``,``response``,``context``å’Œ``done``。 ### 1. 异步接å�£ 异步接å�£æœ¬èº«æ¯”较好ç�†è§£ï¼Œè€Œä¸”用户å�‘请求时ä¸�用带ç�€response的指针,也ä¸�用自己去ä¿�è¯�它的生命周期了~刚æ‰�å·²ç»�看过我们的回调函数``done``,是一个``std::function``,框架会在调用done的生æˆ�代ç �中创建一个response供用户使用。在skeleton代ç �中例å­�是这样的: ```cpp static void echo_done(::example::EchoResponse *response, srpc::RPCContext *context) { // 这个RPCContextå�¯ä»¥æ‹¿åˆ°è¯·æ±‚期间框架å�¯ä»¥æ��供的状æ€� } ``` ### 2. å�Œæ­¥æŽ¥å�£ å�Œæ­¥æŽ¥å�£æ²¡æœ‰ç”Ÿå‘½å‘¨æœŸçš„问题,直接让用户æ“�作``response``å’Œ``context``。 ### 3. å�Šå�Œæ­¥æŽ¥å�£ å�Šå�Œæ­¥æŽ¥å�£ä¼šè¿”回一个**future**,这是Workflow内部å°�装的``WFFuture``,用法与``std::future``类似,需è¦�等待的时候å�¯ä»¥é€šè¿‡``get()``接å�£æ‹¿åˆ°ç»“果,结果就是``response``å’Œ``context``。 ### 4. task接å�£ 其实异步接å�£ï¼Œå†…部也是通过task接å�£å®žçŽ°çš„ã€‚è¿™é‡Œå�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们通过一个client创建task的方å¼�: ```cpp srpc::SRPCClientTask * task = SRPCClient::create_Echo_task(done); task->serialize_input(req); task->start(); ``` 这个方å¼�,充分体现了workflowçš„**二级工厂概念**。srpc对于workflowæ�¥è¯´å°±æ˜¯å‡ ç§�特定的å��议和一些é¢�外的功能(比如用户IDLçš„åº�列化和压缩),所以在workflow实现一个带å¤�æ�‚功能的å��议其实就是实现一个二级工厂。 拿到``task``之å�Žï¼Œéœ€è¦�å¡«``request``并且**start**èµ·æ�¥ï¼Œè¿™éƒ¨åˆ†ä¹Ÿæ˜¯å’Œworkflowçš„å�šæ³•ä¸€æ¨¡ä¸€æ ·çš„ã€‚ 这个接å�£æœ€é‡�è¦�çš„æ„�义是在于: **我们通过client二级工厂创建出æ�¥çš„rpc,å�¯ä»¥ä¸²åˆ°ä»»ä½•workflow的任务æµ�图中,自动éš�æµ�图执行被调起。** ### 5. 利用Context打通workflow任务æµ�,以å�Šå¼‚æ­¥serverçš„å�𿳕 我们在两个地方å�¯ä»¥æ‹¿åˆ°``RPCContext``: * server实现service中的函数å�‚æ•° * clientçš„doneçš„å�‚æ•° 这个RPCContext的功能é�žå¸¸å¤šï¼Œä¸»è¦�是以下三ç§�: 1. server想è¦�给当å‰�这个response设置一些会è¯�级(或者说task级)的信æ�¯çš„æŽ¥å�£ï¼Œæ¯”如压缩类型等; 2. client拿到回å¤�时,查看请求的状æ€�和其他信æ�¯ï¼› 3. 拿到seriesï¼› æ‹¿series的接å�£å¦‚下: ```cpp SeriesWork *get_series() const; ``` 因此,我们在server的函数实现中,就å�¯ä»¥å¾€è¿™ä¸ª``series``å�Žä¸²åˆ«çš„``task``从而实现**异步server**了(serverçš„reply时机为此series上所有任务跑完),也å�¯ä»¥åœ¨æˆ‘作为client的回调函数里继续往任务æµ�å�Žé�¢ä¸²å…¶ä»–workflow任务。这应该是和现有的其他RPC相比最简å�•的异步server实现方å¼�了。 å†�以刚æ‰�çš„service为例å�šå¼‚æ­¥server: ```cpp class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *ctx) override { auto *task = WFTaskFactory::create_timer_task(10, nullptr); ctx->get_series()->push_back(task); // æ­¤RPC的回å¤�时机是这个task执行完,与workflow一致 } }; ``` ### å…­ã€�å…¶ä»–å�¯ä»¥å€Ÿç”¨workflowå�šçš„事情 以上å�¯ä»¥çœ‹åˆ°ï¼Œæˆ‘们顺利地打通workflow,把rpc请求本身也都按照workflow的任务概念划分好了:``å��è®®``ã€�``请求``ã€�``回å¤�``。 那么workflow还有什么功能我们å�¯ä»¥ç”¨å¾—到的呢? 首先是**æœ�务治ç�†**。workflowçš„upstream是本进程内把一批机器绑定到一个域å��下的upstream管ç�†ï¼Œè‡ªå¸¦å¤šç§�æ–¹å¼�的负载å�‡è¡¡å’Œç†”æ–­æ�¢å¤�等机制,srpcçš„client都å�¯ä»¥ç›´æŽ¥æ‹¿æ�¥ç”¨ï¼Œä¸€ä¸ªclient创建出æ�¥å¯¹åº”的是一个ip,或者一个带本地æœ�务治ç�†çš„集群。 **Clientå’Œtask其他层级的é…�ç½®**。比如workflowçš„å�„ç§�超时都å�¯ä»¥ç”¨ä¸Šï¼Œworkflow的网络请求本身有é‡�试次数,rpc默认é…�为0,有需è¦�çš„è¯�用户å�¯ä»¥è‡ªè¡Œé…�置。 **其他系统资æº�**。rpcå�ªæ˜¯ç½‘络相关,而workflow包å�«äº†å¦‚计算和异步文件IO等资æº�,这些在打通了任务æµ�之å�Žï¼Œéƒ½å�¯ä»¥ä¸ºsrpc所用。 ## 七ã€� 写在最å�Ž éš�ç�€srpc架构演进,使用到的泛型编程的方å¼�有如下多ç§�: srpc_multi 架构设计中有些其他细节比如离散内存的管ç�†ã€�内存å��å°„ã€�模å�—化æ�’件的具体å�šæ³•æš‚æ—¶è¿˜ä¸�在本篇wiki中。srpc项目目å‰�在æœ�务层é�¢ä¸Šå�šä¸€äº›æ‹“展和调研,也在研究相关的生æ€�项目。从用户使用和è�½åœ°æƒ…况的良好å��馈æ�¥çœ‹ï¼Œæˆ‘们é�žå¸¸æœ‰ä¿¡å¿ƒç»§ç»­æŽ¨è¿›workflow与srpc的生æ€�,建立起和大家一起学习一起进步的å�ˆä½œæ–¹å¼�。 sogou-srpc-ac65014/src/000077500000000000000000000000001525217424400147355ustar00rootroot00000000000000sogou-srpc-ac65014/src/CMakeLists.txt000066400000000000000000000103271525217424400175000ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) if (NOT "$ENV{LIBRARY_PATH}" STREQUAL "") string(REPLACE ":" ";" LIBRARY_PATH $ENV{LIBRARY_PATH}) set(CMAKE_SYSTEM_LIBRARY_PATH ${LIBRARY_PATH};${CMAKE_SYSTEM_LIBRARY_PATH}) endif () if (NOT "$ENV{CPLUS_INCLUDE_PATH}" STREQUAL "") string(REPLACE ":" ";" INCLUDE_PATH $ENV{CPLUS_INCLUDE_PATH}) set(CMAKE_SYSTEM_INCLUDE_PATH ${INCLUDE_PATH};${CMAKE_SYSTEM_INCLUDE_PATH}) endif () find_package(OpenSSL REQUIRED) set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "") if (WIN32) find_package(Protobuf REQUIRED CONFIG) else () find_package(Protobuf 3.5.0 REQUIRED) endif () if (WIN32) set (HAVE_SYS_UIO_H_01 false) else () set (HAVE_SYS_UIO_H_01 true) endif () if (WITH_VCPKG_TOOLCHAIN) find_package(Workflow REQUIRED CONFIG) else () # Try to find the package if workflow is nor installed, otherwise proceed with find_package() if (NOT WORKFLOW_INSTALLED) find_package(Workflow REQUIRED CONFIG HINTS ../workflow) endif () if (NOT SNAPPY_INSTALLED) set(SNAPPY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/snappy) configure_file(${SNAPPY_DIR}/cmake/config.h.in ${SNAPPY_DIR}/config.h) configure_file(${SNAPPY_DIR}/snappy-stubs-public.h.in ${SNAPPY_DIR}/snappy-stubs-public.h) else () find_package(Snappy 1.1.6 REQUIRED CONFIG) set(SNAPPY_LIB snappy) endif () if (LZ4_INSTALLED) set(LZ4_LIB lz4) endif () endif() include_directories( ${OPENSSL_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ${Protobuf_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR} ${INC_DIR}/srpc ) if (WIN32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /Zc:__cplusplus /std:c++17") else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++17 -fno-exceptions") endif () add_subdirectory(generator) set(SRC rpc_buffer.cc rpc_basic.cc rpc_global.cc ) add_subdirectory(module) add_subdirectory(var) add_subdirectory(thrift) add_subdirectory(compress) add_subdirectory(message) add_dependencies(module LINK_HEADERS) add_dependencies(var LINK_HEADERS) add_dependencies(thrift LINK_HEADERS) add_dependencies(compress LINK_HEADERS) add_dependencies(message LINK_HEADERS) if (NOT WIN32) add_subdirectory(http) add_dependencies(http LINK_HEADERS) endif () if (WIN32) add_library( ${PROJECT_NAME} STATIC ${SRC} $ $ $ $ $ ) add_dependencies(${PROJECT_NAME} LINK_HEADERS) target_compile_definitions( ${PROJECT_NAME} PRIVATE strdup=_strdup strcasecmp=_stricmp strncasecmp=_strnicmp ) install( TARGETS ${PROJECT_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel ) else () set(STATIC_LIB_NAME ${PROJECT_NAME}-static) set(SHARED_LIB_NAME ${PROJECT_NAME}-shared) get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY) link_directories(${OPENSSL_LINK_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) add_library( ${STATIC_LIB_NAME} STATIC ${SRC} $ $ $ $ $ $ ) add_library( ${SHARED_LIB_NAME} SHARED ${SRC} $ $ $ $ $ $ ) if (APPLE) target_link_libraries(${SHARED_LIB_NAME} OpenSSL::SSL OpenSSL::Crypto pthread ${Protobuf_LIBRARY} workflow z ${SNAPPY_LIB} ${LZ4_LIB}) else () target_link_libraries(${SHARED_LIB_NAME}) endif () set_target_properties(${STATIC_LIB_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) set_target_properties(${SHARED_LIB_NAME} PROPERTIES OUTPUT_NAME ${PROJECT_NAME} VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR} ) add_dependencies(${STATIC_LIB_NAME} LINK_HEADERS) add_dependencies(${SHARED_LIB_NAME} LINK_HEADERS) install( TARGETS ${STATIC_LIB_NAME} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel ) install( TARGETS ${SHARED_LIB_NAME} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT devel ) endif () sogou-srpc-ac65014/src/compress/000077500000000000000000000000001525217424400165705ustar00rootroot00000000000000sogou-srpc-ac65014/src/compress/CMakeLists.txt000066400000000000000000000024141525217424400213310ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(compress) set(SRC rpc_compress.cc rpc_compress_snappy.cc ) set_property(SOURCE rpc_compress_snappy.cc APPEND PROPERTY COMPILE_OPTIONS "-fno-rtti") if (WITH_VCPKG_TOOLCHAIN) add_library(${PROJECT_NAME} OBJECT ${SRC}) target_link_libraries(${PROJECT_NAME} lz4 snappy) else () if (SNAPPY_INSTALLED) set(SNAPPY_LIB snappy) else () set(SNAPPY_SRC ../../third_party/snappy/config.h ../../third_party/snappy/snappy-internal.h ../../third_party/snappy/snappy-sinksource.cc ../../third_party/snappy/snappy-sinksource.h ../../third_party/snappy/snappy-stubs-internal.cc ../../third_party/snappy/snappy-stubs-internal.h ../../third_party/snappy/snappy-stubs-public.h ../../third_party/snappy/snappy.cc ../../third_party/snappy/snappy.h ) endif () if (LZ4_INSTALLED) set(LZ4_LIB lz4) else () set(LZ4_SRC ../../third_party/lz4/lib/lz4.c ../../third_party/lz4/lib/lz4.h ../../third_party/lz4/lib/lz4hc.h ../../third_party/lz4/lib/lz4hc.c ../../third_party/lz4/lib/lz4frame.h ../../third_party/lz4/lib/lz4frame.c ../../third_party/lz4/lib/xxhash.c ../../third_party/lz4/lib/xxhash.h ) endif () add_library( ${PROJECT_NAME} OBJECT ${SRC} ${SNAPPY_SRC} ${LZ4_SRC} ) endif() sogou-srpc-ac65014/src/compress/rpc_compress.cc000066400000000000000000000045231525217424400216020ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include "rpc_compress.h" #include "rpc_compress_gzip.h" #include "rpc_compress_snappy.h" #include "rpc_compress_lz4.h" namespace srpc { int RPCCompressor::add(RPCCompressType type) { if (type >= RPCCompressMax || type <= RPCCompressNone) return -2; int ret = 0; if (this->handler[type].compress && this->handler[type].decompress && this->handler[type].lease_size) { ret = 1; } //this->handler[type].type = type; switch (type) { case RPCCompressSnappy: this->handler[type].compress = SnappyManager::SnappyCompress; this->handler[type].decompress = SnappyManager::SnappyDecompress; this->handler[type].compress_iovec = SnappyManager::SnappyCompressIOVec; this->handler[type].decompress_iovec = SnappyManager::SnappyDecompressIOVec; this->handler[type].lease_size = SnappyManager::SnappyLeaseSize; break; case RPCCompressGzip: this->handler[type].compress = GzipCompress; this->handler[type].decompress = GzipDecompress; this->handler[type].compress_iovec = GzipCompressIOVec; this->handler[type].decompress_iovec = GzipDecompressIOVec; this->handler[type].lease_size = GzipLeaseSize; break; case RPCCompressZlib: this->handler[type].compress = ZlibCompress; this->handler[type].decompress = ZlibDecompress; this->handler[type].compress_iovec = ZlibCompressIOVec; this->handler[type].decompress_iovec = ZlibDecompressIOVec; this->handler[type].lease_size = ZlibLeaseSize; break; case RPCCompressLz4: this->handler[type].compress = LZ4Compress; this->handler[type].decompress = LZ4Decompress; this->handler[type].compress_iovec = LZ4CompressIOVec; this->handler[type].decompress_iovec = LZ4DecompressIOVec; this->handler[type].lease_size = LZ4LeaseSize; break; default: ret = -2; break; } return ret; } } // namespace srpc sogou-srpc-ac65014/src/compress/rpc_compress.h000066400000000000000000000130271525217424400214430ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_COMPRESS_H__ #define __RPC_COMPRESS_H__ #include "rpc_basic.h" #include "rpc_options.h" namespace srpc { using CompressFunction = int (*)(const char*, size_t, char *, size_t); using DecompressFunction = int (*)(const char *, size_t, char *, size_t); using CompressIOVecFunction = int (*)(RPCBuffer *, RPCBuffer *); using DempressIOVecFunction = int (*)(RPCBuffer *, RPCBuffer *); using LeaseSizeFunction = int (*)(size_t); class CompressHandler { public: CompressHandler() { //this->type = RPCCompressNone; this->compress = nullptr; this->decompress = nullptr; this->compress_iovec = nullptr; this->decompress_iovec = nullptr; this->lease_size = nullptr; } //int type; CompressFunction compress; DecompressFunction decompress; CompressIOVecFunction compress_iovec; DempressIOVecFunction decompress_iovec; LeaseSizeFunction lease_size; }; class RPCCompressor { public: static RPCCompressor *get_instance() { static RPCCompressor kInstance; return &kInstance; } public: // parse message from compressed data // -1: error // -2, invalid compress type or decompress function int parse_from_compressed(const char *buf, size_t buflen, char *msg, size_t msglen, int type) const; int parse_from_compressed(RPCBuffer *src, RPCBuffer *dest, int type) const; // serialized to compressed data // -1: error // -2, invalid compress type or compress function int serialize_to_compressed(const char *msg, size_t msglen, char *buf, size_t buflen, int type) const; int serialize_to_compressed(RPCBuffer *src, RPCBuffer *dest, int type) const; /* * ret: >0: the theoretically lease size of compressed data * -1: error * -2, invalid compress type or lease_size function */ int lease_compressed_size(int type, size_t origin_size) const; /* * Add existed compress_type * ret: 0, success * 1, handler existed and update success * -2, invalid compress type */ int add(RPCCompressType type); /* * Add self-define compress algorithm * ret: 0, success * 1, handler existed and update success * -2, invalid compress type or compress/decompress/lease_size function */ int add_handler(int type, CompressHandler&& handler); const CompressHandler *find_handler(int type) const; // clear all the registed handler void clear(); private: RPCCompressor() { this->add(RPCCompressGzip); this->add(RPCCompressZlib); this->add(RPCCompressSnappy); this->add(RPCCompressLz4); } CompressHandler handler[RPCCompressMax]; }; //////// // inl inline int RPCCompressor::add_handler(int type, CompressHandler&& handler) { if (type >= RPCCompressMax || type <= RPCCompressNone) return -2; if (!handler.compress || !handler.decompress || !handler.lease_size) return -2; int ret = 0; if (this->handler[type].compress && this->handler[type].decompress && this->handler[type].lease_size) { ret = 1; } this->handler[type] = std::move(handler); return ret; } inline const CompressHandler *RPCCompressor::find_handler(int type) const { if (type >= RPCCompressMax || type <= RPCCompressNone) { return NULL; } if (!this->handler[type].compress || !this->handler[type].decompress || !this->handler[type].lease_size) { return NULL; } return &this->handler[type]; } inline int RPCCompressor::parse_from_compressed(const char *buf, size_t buflen, char *msg, size_t msglen, int type) const { if (type >= RPCCompressMax || type <= RPCCompressNone || !this->handler[type].decompress) { return -2; } return this->handler[type].decompress(buf, buflen, msg, msglen); } inline int RPCCompressor::parse_from_compressed(RPCBuffer *src, RPCBuffer *dest, int type) const { if (type >= RPCCompressMax || type <= RPCCompressNone || !this->handler[type].decompress_iovec) { return -2; } return this->handler[type].decompress_iovec(src, dest); } inline int RPCCompressor::serialize_to_compressed(const char *msg, size_t msglen, char *buf, size_t buflen, int type) const { if (type >= RPCCompressMax || type <= RPCCompressNone || !this->handler[type].compress) { return -2; } return this->handler[type].compress(msg, msglen, buf, buflen); } inline int RPCCompressor::serialize_to_compressed(RPCBuffer *src, RPCBuffer *dest, int type) const { if (type >= RPCCompressMax || type <= RPCCompressNone || !this->handler[type].compress) { return -2; } return this->handler[type].compress_iovec(src, dest); } inline int RPCCompressor::lease_compressed_size(int type, size_t origin_size) const { if (type >= RPCCompressMax || type <= RPCCompressNone || !this->handler[type].lease_size) { return -2; } return this->handler[type].lease_size(origin_size); } inline void RPCCompressor::clear() { for (int i = 0; i < RPCCompressMax; i++) { //this->handler[i].type = RPCCompressNone; this->handler[i].compress = nullptr; this->handler[i].decompress = nullptr; this->handler[i].lease_size = nullptr; } } } // namespace srpc #endif sogou-srpc-ac65014/src/compress/rpc_compress_gzip.h000066400000000000000000000226531525217424400225010ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_COMPRESS_GZIP_H__ #define __RPC_COMPRESS_GZIP_H__ //#include //#include #include #include "rpc_basic.h" //#define COMPRESS_LEVEL 6 //#define ZLIB_LEASE_HEADER 100 #define GzipDecompress CommonDecompress #define ZlibDecompress CommonDecompress #define GzipDecompressIOVec CommonDecompressIOVec #define ZlibDecompressIOVec CommonDecompressIOVec namespace srpc { static constexpr int GZIP_LEASE_HEADER = 20; static constexpr int WINDOW_BITS = 15; static constexpr int OPTION_FORMAT_ZLIB = 0; static constexpr int OPTION_FORMAT_GZIP = 16; static constexpr int OPTION_FORMAT_AUTO = 32; /* static int protobuf_stream_compress(const ProtobufIDLMessage* msg, char *buf, size_t buflen, int compress_type) { google::protobuf::io::GzipOutputStream::Options options; switch(compress_type) { case RPCCompressGzip: options.format = google::protobuf::io::GzipOutputStream::GZIP; break; case RPCCompressZlib: options.format = google::protobuf::io::GzipOutputStream::ZLIB; break; default: return -1; } options.compression_level = COMPRESS_LEVEL; google::protobuf::io::ArrayOutputStream arr(buf, buflen); google::protobuf::io::GzipOutputStream gzipOutputStream(&arr, options); if (!msg->SerializeToZeroCopyStream(&gzipOutputStream)) return -1; gzipOutputStream.Flush(); gzipOutputStream.Close(); return arr.ByteCount(); } static int protobuf_stream_decompress(const char *buf, size_t buflen, ProtobufIDLMessage* msg, size_t msglen, int compress_type) { google::protobuf::io::GzipInputStream::Format format; switch(compress_type) { case RPCCompressGzip: format = google::protobuf::io::GzipInputStream::GZIP; break; case RPCCompressZlib: format = google::protobuf::io::GzipInputStream::ZLIB; break; default: return -1; } google::protobuf::io::ArrayInputStream arr(buf, buflen); google::protobuf::io::GzipInputStream gzipInputStream(&arr, format); if (!msg->ParseFromZeroCopyStream(&gzipInputStream)) return -1; return msg->ByteSizeLong(); } */ static int CommonCompress(const char *msg, size_t msglen, char *buf, size_t buflen, int option_format) { if (!msg) return 0; z_stream c_stream; c_stream.zalloc = (alloc_func)0; c_stream.zfree = (free_func)0; c_stream.opaque = (voidpf)0; if(deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, WINDOW_BITS | option_format, 8, Z_DEFAULT_STRATEGY) != Z_OK) { return -1; } c_stream.next_in = (Bytef *)msg; c_stream.avail_in = msglen; c_stream.next_out = (Bytef *)buf; c_stream.avail_out = (uInt)buflen; while (c_stream.avail_in != 0 && c_stream.total_in < buflen) { if (deflate(&c_stream, Z_NO_FLUSH) != Z_OK) { deflateEnd(&c_stream); return -1; } } if (c_stream.avail_in != 0) return c_stream.avail_in; for (;;) { int err = deflate(&c_stream, Z_FINISH); if(err == Z_STREAM_END) break; if(err != Z_OK) { deflateEnd(&c_stream); return -1; } } if (deflateEnd(&c_stream) != Z_OK) return -1; return c_stream.total_out; } /* * compress serialized msg into buf. * ret: -1: failed * >0: byte count of compressed data */ static inline int GzipCompress(const char *msg, size_t msglen, char *buf, size_t buflen) { return CommonCompress(msg, msglen, buf, buflen, OPTION_FORMAT_GZIP); } /* * compress serialized msg into buf. * ret: -1: failed * >0: byte count of compressed data */ static inline int ZlibCompress(const char *msg, size_t msglen, char *buf, size_t buflen) { return CommonCompress(msg, msglen, buf, buflen, OPTION_FORMAT_ZLIB); } static constexpr unsigned char dummy_head[2] = { 0xB + 0x8 * 0x10, (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF, }; /* * decompress and parse buf into msg * ret: -1: failed * >0: byte count of compressed data */ static int CommonDecompress(const char *buf, size_t buflen, char *msg, size_t msglen) { int err; z_stream d_stream = {0}; /* decompression stream */ d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; d_stream.next_in = (Bytef *)buf; d_stream.avail_in = 0; d_stream.next_out = (Bytef *)msg; if (inflateInit2(&d_stream, WINDOW_BITS | OPTION_FORMAT_AUTO) != Z_OK) return -1; while (d_stream.total_out < msglen && d_stream.total_in < buflen) { d_stream.avail_in = d_stream.avail_out = (uInt)msglen; err = inflate(&d_stream, Z_NO_FLUSH); if(err == Z_STREAM_END) break; if (err != Z_OK) { if (err != Z_DATA_ERROR) { inflateEnd(&d_stream); return -1; } d_stream.next_in = (Bytef*) dummy_head; d_stream.avail_in = sizeof (dummy_head); if (inflate(&d_stream, Z_NO_FLUSH) != Z_OK) { inflateEnd(&d_stream); return -1; } } } if (inflateEnd(&d_stream) != Z_OK) return -1; return d_stream.total_out; } static int CommonCompressIOVec(RPCBuffer *src, RPCBuffer *dst, int option_format) { z_stream c_stream; int err; size_t total_alloc = 0; const void *in; void *out; size_t buflen = src->size(); size_t out_len = buflen; c_stream.zalloc = (alloc_func)0; c_stream.zfree = (free_func)0; c_stream.opaque = (voidpf)0; if (deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, WINDOW_BITS | option_format, 8, Z_DEFAULT_STRATEGY) != Z_OK) { return -1; } c_stream.avail_in = 0; c_stream.avail_out = 0; while (c_stream.total_in < buflen) { if (c_stream.avail_in == 0) { if ((c_stream.avail_in = (uInt)src->fetch(&in)) == 0) { deflateEnd(&c_stream); return -1; } c_stream.next_in = static_cast(const_cast(in)); } if (c_stream.avail_out == 0) { if (dst->acquire(&out, &out_len) == false) { deflateEnd(&c_stream); return -1; } total_alloc += out_len; c_stream.next_out = static_cast(out); c_stream.avail_out = (uInt)out_len; } if (deflate(&c_stream, Z_NO_FLUSH) != Z_OK) { deflateEnd(&c_stream); return -1; } } // if (c_stream.avail_in != 0) // return c_stream.avail_in; for (;;) { if (c_stream.avail_out == 0) { if (dst->acquire(&out, &out_len) == false) return -1; total_alloc += out_len; c_stream.next_out = static_cast(out); c_stream.avail_out = (uInt)out_len; } err = deflate(&c_stream, Z_FINISH); if(err == Z_STREAM_END) break; if(err != Z_OK) { deflateEnd(&c_stream); return -1; } } if (deflateEnd(&c_stream) != Z_OK) return -1; dst->backup(total_alloc - c_stream.total_out); return c_stream.total_out; } /* * compress serialized RPCBuffer src(Source) into RPCBuffer dst(Sink). * ret: -1: failed * >0: byte count of compressed data */ static int GzipCompressIOVec(RPCBuffer *src, RPCBuffer *dst) { return CommonCompressIOVec(src, dst, OPTION_FORMAT_GZIP); } /* * compress serialized RPCBuffer src(Source) into RPCBuffer dst(Sink). * ret: -1: failed * >0: byte count of compressed data */ static int ZlibCompressIOVec(RPCBuffer *src, RPCBuffer *dst) { return CommonCompressIOVec(src, dst, OPTION_FORMAT_ZLIB); } /* * decompress RPCBuffer src(Source) into RPCBuffer dst(Sink). * ret: -1: failed * >0: byte count of compressed data */ static int CommonDecompressIOVec(RPCBuffer *src, RPCBuffer *dst) { int err; z_stream d_stream = { 0 }; /* decompression stream */ size_t total_alloc = 0; const void *in; void *out; size_t buflen = src->size(); size_t out_len = buflen; d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; if (inflateInit2(&d_stream, WINDOW_BITS | OPTION_FORMAT_AUTO) != Z_OK) return -1; d_stream.avail_in = 0; d_stream.avail_out = 0; while (d_stream.total_in < buflen) { if (d_stream.avail_in == 0) { if ((d_stream.avail_in = (uInt)src->fetch(&in)) == 0) { inflateEnd(&d_stream); return -1; } d_stream.next_in = static_cast(const_cast(in)); } if (d_stream.avail_out == 0) { if (dst->acquire(&out, &out_len) == false) { inflateEnd(&d_stream); return -1; } total_alloc += out_len; d_stream.next_out = static_cast(out); d_stream.avail_out = (uInt)out_len; } err = inflate(&d_stream, Z_NO_FLUSH); if (err == Z_STREAM_END) break; if (err != Z_OK) { if (err != Z_DATA_ERROR) { inflateEnd(&d_stream); return -1; } d_stream.next_in = (Bytef*) dummy_head; d_stream.avail_in = sizeof (dummy_head); if (inflate(&d_stream, Z_NO_FLUSH) != Z_OK) { inflateEnd(&d_stream); return -1; } } } if (inflateEnd(&d_stream) != Z_OK) return -1; dst->backup(total_alloc - d_stream.total_out); return d_stream.total_out; } /* * lease size after compress origin_size data */ static int GzipLeaseSize(size_t origin_size) { return (int)(origin_size + 5 * (origin_size / 16383 + 1) + GZIP_LEASE_HEADER); } static int ZlibLeaseSize(size_t origin_size) { return compressBound((uLong)origin_size); } } // end namespace srpc #endif sogou-srpc-ac65014/src/compress/rpc_compress_lz4.h000066400000000000000000000142331525217424400222340ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_COMPRESS_LZ4_H__ #define __RPC_COMPRESS_LZ4_H__ #include "lz4.h" #include "lz4frame.h" #include "rpc_basic.h" namespace srpc { //#define IN_CHUNK_SIZE (16*1024) static constexpr LZ4F_preferences_t kPrefs = { { LZ4F_max256KB, LZ4F_blockLinked, LZ4F_noContentChecksum, LZ4F_frame, 0 /* unknown content size */, 0 /* no dictID */ , LZ4F_noBlockChecksum }, 0, /* compression level; 0 == default */ 0, /* autoflush */ 0, /* favor decompression speed */ { 0, 0, 0 }, /* reserved, must be set to 0 */ }; /* static size_t get_block_size(const LZ4F_frameInfo_t* info) { switch (info->blockSizeID) { case LZ4F_default: case LZ4F_max64KB: return 1 << 16; case LZ4F_max256KB: return 1 << 18; case LZ4F_max1MB: return 1 << 20; case LZ4F_max4MB: return 1 << 22; default: printf("Impossible with expected frame specification (<=v1.6.1)\n"); } return 0; } */ /* * compress serialized msg into buf. * ret: -1: failed * >0: byte count of compressed data */ static int LZ4Compress(const char* msg, size_t msglen, char *buf, size_t buflen) { int ret = LZ4_compress_default(msg, buf, (int)msglen, (int)buflen); if (ret < 0) ret = -1; return ret; } /* * decompress and parse buf into msg * ret: -1: failed * >0: byte count of compressed data */ static int LZ4Decompress(const char *buf, size_t buflen, char *msg, size_t msglen) { int ret = LZ4_decompress_safe(buf, msg, (int)buflen, (int)msglen); if (ret < 0) return -1; return ret; } /* * compress RPCBuffer src into RPCBuffer dst * ret: -1: failed * >0: byte count of compressed data */ static int LZ4CompressIOVec(RPCBuffer *src, RPCBuffer *dst) { const void *in_buf; size_t in_len; void *out_buf; size_t out_len; size_t total_out; size_t header_size; size_t compressed_size; LZ4F_cctx *ctx = NULL; size_t const ctx_status = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); if (LZ4F_isError(ctx_status)) { LZ4F_freeCompressionContext(ctx); return -1; } out_len = LZ4F_HEADER_SIZE_MAX; if (dst->acquire(&out_buf, &out_len) == false) { LZ4F_freeCompressionContext(ctx); return -1; } // write frame header header_size = LZ4F_compressBegin(ctx, out_buf, out_len, &kPrefs); if (LZ4F_isError(header_size)) { LZ4F_freeCompressionContext(ctx); return -1; } dst->backup(out_len - header_size); total_out = header_size; //int count = 0; // write every in_buf while ((in_len = src->fetch(&in_buf)) != 0) { //count++; out_len = LZ4F_compressBound(in_len, &kPrefs); if (dst->acquire(&out_buf, &out_len) == false) { LZ4F_freeCompressionContext(ctx); return -1; } compressed_size = LZ4F_compressUpdate(ctx, out_buf, out_len, in_buf, in_len, NULL); if (LZ4F_isError(compressed_size)) { LZ4F_freeCompressionContext(ctx); return -1; } dst->backup(out_len - compressed_size); total_out += compressed_size; } // flush whatever remains within internal buffers out_len = LZ4F_compressBound(0, &kPrefs); if (dst->acquire(&out_buf, &out_len) == false) { LZ4F_freeCompressionContext(ctx); return -1; } compressed_size = LZ4F_compressEnd(ctx, out_buf, out_len, NULL); if (LZ4F_isError(compressed_size)) { LZ4F_freeCompressionContext(ctx); return -1; } dst->backup(out_len - compressed_size); total_out += compressed_size; LZ4F_freeCompressionContext(ctx); return (int)total_out; } /* * decompress RPCBuffer src into RPCBuffer dst * ret: -1: failed * >0: byte count of compressed data */ static int LZ4DecompressIOVec(RPCBuffer *src, RPCBuffer *dst) { const void *in_buf; size_t in_len; void *out_buf; size_t out_len; size_t consumed_len; size_t decompressed_size; size_t total_out = 0; LZ4F_dctx *dctx = NULL; size_t const dctx_status = LZ4F_createDecompressionContext(&dctx, LZ4F_VERSION); if (LZ4F_isError(dctx_status)) { LZ4F_freeDecompressionContext(dctx); return -1; } // get framed info LZ4F_frameInfo_t info; memset(&info, 0, sizeof (info)); in_len = src->fetch(&in_buf); consumed_len = in_len; size_t const frame_info_ret = LZ4F_getFrameInfo(dctx, &info, in_buf, &consumed_len); if (LZ4F_isError(frame_info_ret)) { LZ4F_freeDecompressionContext(dctx); return -1; } /* size_t const dest_capacity = get_block_size(&info); if (dest_capacity == 0) { LZ4F_freeDecompressionContext(ctx); return -1; } fprintf(stderr, "get_block_size=%d\n", dest_capacity); */ size_t ret = 1; bool first_chunk = true; const char *start; const char *end; while (ret != 0) { if (first_chunk) { in_len = in_len - consumed_len; in_buf = (const char *)in_buf + consumed_len; first_chunk = false; } else { in_len = src->fetch(&in_buf); } out_len = in_len; start = (const char *)in_buf; end = (const char *)in_buf + in_len; consumed_len = 0; while (start != end && ret != 0) { if (dst->acquire(&out_buf, &out_len) == false) { LZ4F_freeDecompressionContext(dctx); return -1; } decompressed_size = out_len; consumed_len = end - start; ret = LZ4F_decompress(dctx, out_buf, &decompressed_size, start, &consumed_len, NULL); if (LZ4F_isError(ret)) { LZ4F_freeDecompressionContext(dctx); return -1; } start = start + consumed_len; dst->backup(out_len - decompressed_size); total_out += decompressed_size; } if (start != end) { LZ4F_freeDecompressionContext(dctx); return -1; } } LZ4F_freeDecompressionContext(dctx); return (int)total_out; } /* * lease size after compress origin_size data */ static int LZ4LeaseSize(size_t origin_size) { return LZ4_compressBound((int)origin_size); } } // end namespace srpc #endif sogou-srpc-ac65014/src/compress/rpc_compress_snappy.cc000066400000000000000000000052141525217424400231720ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include "snappy.h" #include "snappy-sinksource.h" #include "rpc_compress_snappy.h" namespace srpc { class RPCSnappySink : public snappy::Sink { public: RPCSnappySink(RPCBuffer *buf) { this->buf = buf; } void Append(const char *bytes, size_t n) override { this->buf->append(bytes, n, BUFFER_MODE_COPY); } size_t size() const { return this->buf->size(); } private: RPCBuffer *buf; }; class RPCSnappySource : public snappy::Source { public: RPCSnappySource(RPCBuffer *buf) { this->buf = buf; this->buf_size = this->buf->size(); this->pos = 0; } size_t Available() const override { return this->buf_size - this->pos; } const char *Peek(size_t *len) override { const void *pos; *len = this->buf->peek(&pos); return (const char *)pos; } void Skip(size_t n) override { this->pos += this->buf->seek(n); } private: RPCBuffer *buf; size_t buf_size; size_t pos; }; int SnappyManager::SnappyCompress(const char *msg, size_t msglen, char *buf, size_t buflen) { size_t compressed_len = buflen; snappy::RawCompress(msg, msglen, buf, &compressed_len); if (compressed_len > buflen) return -1; return (int)compressed_len; } int SnappyManager::SnappyDecompress(const char *buf, size_t buflen, char *msg, size_t msglen) { size_t origin_len; if (snappy::GetUncompressedLength(buf, buflen, &origin_len) && origin_len <= msglen && snappy::RawUncompress(buf, buflen, msg)) { return (int)origin_len; } return -1; } int SnappyManager::SnappyCompressIOVec(RPCBuffer *src, RPCBuffer *dst) { RPCSnappySource source(src); RPCSnappySink sink(dst); return (int)snappy::Compress(&source, &sink); } int SnappyManager::SnappyDecompressIOVec(RPCBuffer *src, RPCBuffer *dst) { RPCSnappySource source(src); RPCSnappySink sink(dst); // if (snappy::IsValidCompressed(&source)) // if (snappy::GetUncompressedLength(&source, &origin_len)) return (int)snappy::Uncompress(&source, &sink) ? sink.size() : -1; } int SnappyManager::SnappyLeaseSize(size_t origin_size) { return (int)snappy::MaxCompressedLength(origin_size); } } // end namespace srpc sogou-srpc-ac65014/src/compress/rpc_compress_snappy.h000066400000000000000000000031631525217424400230350ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_COMPRESS_SNAPPY_H__ #define __RPC_COMPRESS_SNAPPY_H__ #include "rpc_buffer.h" namespace srpc { class SnappyManager { public: /* * compress serialized msg into buf. * ret: -1: failed * >0: byte count of compressed data */ static int SnappyCompress(const char *msg, size_t msglen, char *buf, size_t buflen); /* * decompress and parse buf into msg * ret: -1: failed * >0: byte count of compressed data */ static int SnappyDecompress(const char *buf, size_t buflen, char *msg, size_t msglen); /* * compress RPCBuffer src(Source) into RPCBuffer dst(Sink) * ret: -1: failed * >0: byte count of compressed data */ static int SnappyCompressIOVec(RPCBuffer *src, RPCBuffer *dst); /* * decompress RPCBuffer src(Source) into RPCBuffer dst(Sink) * ret: -1: failed * >0: byte count of compressed data */ static int SnappyDecompressIOVec(RPCBuffer *src, RPCBuffer *dst); /* * lease size after compress origin_size data */ static int SnappyLeaseSize(size_t origin_size); }; } // end namespace srpc #endif sogou-srpc-ac65014/src/generator/000077500000000000000000000000001525217424400167235ustar00rootroot00000000000000sogou-srpc-ac65014/src/generator/CMakeLists.txt000066400000000000000000000006301525217424400214620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(srpc_generator) set(SRC compiler.cc generator.cc parser.cc ) add_executable(${PROJECT_NAME} ${SRC}) if (WIN32) target_compile_definitions( ${PROJECT_NAME} PRIVATE getcwd=_getcwd strdup=_strdup strcasecmp=_stricmp strncasecmp=_strnicmp ) endif () install( TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT devel ) sogou-srpc-ac65014/src/generator/compiler.cc000066400000000000000000000140771525217424400210550ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #ifdef _WIN32 #include #define MAXPATHLEN 4096 #else #include #include #include #endif #include "generator.h" const char *SRPC_VERSION = "0.10.5"; /* LQ - prototype to determine if the file type is thrift */ enum { TYPE_UNKNOWN = -1, TYPE_PROTOBUF = 0, TYPE_THRIFT = 1, }; static int check_file_idl_type(const char *filename); static int parse_origin(int argc, const char *argv[], struct GeneratorParams& params, int& idl_type); #ifndef _WIN32 static int parse_getopt(int argc, char * const *argv, struct GeneratorParams& params, int& idl_type); #endif static int parse_prefix_dir(std::string& file, std::string& dir); static int get_idl_type(const char *argv); static bool is_root(std::string& file); const char *SRPC_GENERATOR_USAGE = "\ Usage:\n\ %s [protobuf|thrift] \n\n\ Available options(linux):\n\ -f, --idl_file : IDL file name. If multiple files are imported, specify the top one. Will parse recursively.\n\ -o, --output_dir : Output directory.\n\ -i, --input_dir : Specify the directory in which to search for imports.\n\ -s, --skip_skeleton : Skip generating skeleton file. (default: generate)\n\ -v, --version : Show version.\n\ -h, --help : Show usage.\n"; int main(int argc, const char *argv[]) { int idl_type = TYPE_UNKNOWN; struct GeneratorParams params; #ifndef _WIN32 if (parse_origin(argc, argv, params, idl_type) == 0) { if (parse_getopt(argc, (char * const *)argv, params, idl_type) != 0) return 0; } #else parse_origin(argc, argv, params, idl_type); #endif if (params.out_dir == NULL || params.idl_file.empty()) { fprintf(stderr, SRPC_GENERATOR_USAGE, argv[0]); return 0; } if (idl_type == TYPE_UNKNOWN) idl_type = check_file_idl_type(params.idl_file.data()); if (is_root(params.idl_file)) { if (params.input_dir.empty()) parse_prefix_dir(params.idl_file, params.input_dir); else { fprintf(stderr, "ERROR: input_dir must encompasses the idl_file %s\n", params.idl_file.c_str()); return 0; } } else { if (params.input_dir.empty() || is_root(params.input_dir) == false) { char current_dir[MAXPATHLEN] = {}; getcwd(current_dir, MAXPATHLEN); current_dir[strlen(current_dir)] = '/'; if (params.input_dir.empty()) params.input_dir = current_dir; else params.input_dir = current_dir + params.input_dir; } } Generator gen(idl_type == TYPE_THRIFT ? true : false); fprintf(stdout, "[Generator Begin]\n"); gen.generate(params); fprintf(stdout, "[Generator Done]\n"); return 0; } int parse_origin(int argc, const char *argv[], struct GeneratorParams& params, int& idl_type) { int idl_file_id = 1; for (int i = 1; i < argc && i < 4 && argv[i][0] != '-'; i++) { if (i == 1) // parse [protobuf|thrift] { idl_type = get_idl_type(argv[1]); if (idl_type != TYPE_UNKNOWN) { idl_file_id++; continue; } } if (i == idl_file_id) // parse { int tmp_type = check_file_idl_type(argv[i]); if (tmp_type == TYPE_UNKNOWN) { fprintf(stderr, "ERROR: Invalid IDL file \"%s\".\n", argv[i]); return -1; } if (idl_type == TYPE_UNKNOWN) idl_type = tmp_type; params.idl_file = argv[idl_file_id]; } else // parse params.out_dir = argv[i]; } return 0; } #ifndef _WIN32 int parse_getopt(int argc, char * const *argv, struct GeneratorParams& params, int& idl_type) { int ch; static struct option longopts[] = { { "version", no_argument, NULL, 'v'}, { "idl_file", required_argument, NULL, 'f'}, { "output_dir", required_argument, NULL, 'o'}, { "input_dir", required_argument, NULL, 'i'}, { "skip_skeleton", no_argument, NULL, 's'}, { "help", no_argument, NULL, 'h'} }; while ((ch = getopt_long(argc, argv, "vf:o:i:sh", longopts, NULL)) != -1) { switch (ch) { case 'v': fprintf(stderr, "srpc_generator version %s\n", SRPC_VERSION); return 1; case 'f': if (!params.idl_file.empty()) { fprintf(stderr, "Error: -i is duplicate with idl_file %s\n", params.idl_file.c_str()); return -1; } params.idl_file = optarg; break; case 'o': if (params.out_dir != NULL) { fprintf(stderr, "Error: -o is duplicate with output_dir %s\n", params.out_dir); return -1; } params.out_dir = optarg; break; case 'i': params.input_dir = optarg; if (params.input_dir.at(params.input_dir.length() - 1) != '/') params.input_dir += '/'; break; case 's': params.generate_skeleton = false; break; case 'h': break; default: return 0; } } return 0; } #endif int get_idl_type(const char *type) { if (strcasecmp(type, "protobuf") == 0) return TYPE_PROTOBUF; if (strcasecmp(type, "thrift") == 0) return TYPE_THRIFT; return TYPE_UNKNOWN; } // idl file will not start with - int check_file_idl_type(const char *filename) { size_t len = strlen(filename); if (len > 6 && strcmp(filename + len - 6, ".proto") == 0) return TYPE_PROTOBUF; else if (len > 7 && strcmp(filename + len - 7, ".thrift") == 0) return TYPE_THRIFT; return TYPE_UNKNOWN; } int parse_prefix_dir(std::string& file, std::string& dir) { auto pos = file.find_last_of('/'); if (pos == std::string::npos) return -1; dir = file.substr(0, pos + 1); file = file.substr(pos + 1); return 0; } bool is_root(std::string& file) { if (file[0] == '/' || file[1] == ':') return true; return false; } sogou-srpc-ac65014/src/generator/descriptor.h000066400000000000000000000127461525217424400212640ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_DESCRIPTOR_H__ #define __RPC_DESCRIPTOR_H__ #include #include #include struct rpc_param { std::string type_name; std::string var_name; std::string default_value; int16_t field_id; int8_t data_type; int8_t required_state; }; struct rpc_descriptor { std::string method_name; std::string request_name; std::string response_name; std::vector req_params; std::vector resp_params; }; struct struct_descriptor { std::vector params; }; struct typedef_descriptor { std::string old_type_name; std::string new_type_name; }; struct Descriptor { std::string block_name; std::string block_type; //service extends std::string extends_type; // serivce std::vector rpcs; // struct struct_descriptor st; // enum std::vector enum_lines; }; //class ServiceDescrpitor : public Descriptor //{}; struct idl_info { std::string file_name; std::string input_dir; std::string absolute_file_path; std::string file_name_prefix; std::vector package_name; std::list desc_list; std::list include_list; std::list typedef_list; //typedef name -> real cpptype std::map typedef_mapping; }; class SGenUtil { public: static const char *skip_string(const char *cursor) { while (*cursor && *cursor != '\"') { if (*cursor == '\\') { cursor++; if (!*cursor) break; } cursor++; } return cursor; } static std::vector split_skip_string(const std::string& str, char sep) { std::vector res; /* Can not use '\"' as the seperator. */ if (sep == '\"') return res; const char *cursor = str.c_str(); const char *start = cursor; while (*cursor) { if (*cursor == '\"') { cursor = SGenUtil::skip_string(++cursor); if (!*cursor) break; } else if (*cursor == sep) { if (start < cursor) res.emplace_back(start, cursor); start = cursor + 1; } cursor++; } if (start < cursor) res.emplace_back(start, cursor); return res; } /* static std::vector split_filter_empty(const std::string& str, char sep) { std::vector res; std::string::const_iterator start = str.begin(); std::string::const_iterator end = str.end(); std::string::const_iterator next = find(start, end, sep); while (next != end) { if (start < next) res.emplace_back(start, next); start = next + 1; next = find(start, end, sep); } if (start < next) res.emplace_back(start, next); return res; } */ static std::vector split_by_space(const std::string& str) { std::vector elems; size_t offset = 0; size_t start = 0; size_t end = 0; while(offset < str.size()) { start = SGenUtil::find_next_nonspace(str,offset); if (start == std::string::npos) break; end = SGenUtil::find_next_space(str,start+1); if (end == std::string::npos) { elems.push_back(str.substr(start)); break; } elems.push_back(str.substr(start,end-start)); offset = end+1; } return elems; } static std::string strip(const std::string& str) { std::string res; if (!str.empty()) { const char *st = str.c_str(); const char *ed = st + str.size() - 1; while (st <= ed) { if (!isspace(*st)) break; st++; } while (ed >= st) { if (!isspace(*ed)) break; ed--; } if (ed >= st) res.assign(st, ed - st + 1); } return res; } static std::string lstrip(const std::string& str) { std::string res; if (!str.empty()) { const char *st = str.c_str(); const char *ed = st + str.size(); while (st < ed) { if (!isspace(*st)) break; st++; } if (st < ed) res.assign(st, ed - st); } return res; } static bool start_with(const std::string &str, const std::string prefix) { size_t prefix_len = prefix.size(); if (str.size() < prefix_len) return false; for (size_t i = 0; i < prefix_len; i++) { if (str[i] != prefix[i]) return false; } return true; } static void replace(std::string& str, const std::string& before, const std::string& after) { for (size_t pos = 0; pos < str.size(); pos += after.length()) { pos = str.find(before, pos); if (pos != std::string::npos) str.replace(pos, before.length(), after); else break; } } static size_t find_next_nonspace(const std::string& str, size_t pos) { for(size_t i = pos; i < str.size(); i++) { if (!std::isspace(str[i])) return i; } return std::string::npos; } static size_t find_next_space(const std::string& str, size_t pos) { for(size_t i = pos; i < str.size(); i++) { if (std::isspace(str[i])) return i; } return std::string::npos; } static std::set *get_enum_set() { static std::set kInstance; return &kInstance; } }; #endif sogou-srpc-ac65014/src/generator/generator.cc000066400000000000000000000306441525217424400212270ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include "generator.h" bool Generator::init_file_names(const std::string& idl_file, const char *out_dir) { if (idl_file.size() <= 5) return false; const char *p; if (this->is_thrift) { p = idl_file.c_str() + idl_file.size() - 7; if (strncmp(p, ".thrift", 7) != 0) return false; } else { p = idl_file.c_str() + idl_file.size() - 6; if (strncmp(p, ".proto", 6) != 0) return false; } p = idl_file.c_str() + idl_file.size() - 1; while (p > idl_file.c_str()) { if (*p == '/') break; p--; } if (*p == '/') p++; std::string str = p; auto pos = str.find("."); if (pos == std::string::npos) return false; this->prefix = str.substr(0, pos); str = out_dir; if (str[str.length() - 1] != '/') str.append("/"); this->out_dir = str; this->srpc_file = str; this->srpc_file.append(this->prefix); this->srpc_file.append(this->suffix); this->srpc_file.append("h"); this->thrift_type_file = str; this->thrift_type_file.append(this->prefix); this->thrift_type_file.append(this->thrift_suffix); this->thrift_type_file.append("h"); fprintf(stdout, "[Generator] generate srpc files: %s %s\n", this->srpc_file.c_str(), this->is_thrift ? this->thrift_type_file.c_str() : ""); return true; } bool Generator::generate(struct GeneratorParams& params) { this->info.input_dir = params.input_dir; if (this->parser.parse(params.idl_file, this->info) == false) { fprintf(stderr, "[Generator Error] parse failed.\n"); return false; } bool include_service_define = false; for (auto& include_info : this->info.include_list) { if (!include_info.desc_list.empty()) { include_service_define = true; break; } } if (this->info.desc_list.empty() && !include_service_define) { fprintf(stdout, "[Generator Error] No 'service' definition.\n"); return false; } if (this->generate_header(this->info, params) == false) { fprintf(stderr, "[Generator Error] generate failed.\n"); return false; } if (params.generate_skeleton == true) this->generate_skeleton(this->info.file_name); return true; } bool Generator::generate_header(idl_info& cur_info, struct GeneratorParams& params) { for (auto& sub_info : cur_info.include_list) { fprintf(stdout, "[Generator] auto generate include file [%s]\n", sub_info.absolute_file_path.c_str()); if (!this->generate_header(sub_info, params)) return false; } // for protobuf: if no [rpc], don`t need to generate xxx.srpc.h if (this->init_file_names(cur_info.absolute_file_path, params.out_dir) == false) { fprintf(stderr, "[Generator Error] init file name failed. %s %s\n", cur_info.absolute_file_path.c_str(), params.out_dir); return false; } // will generate skeleton file only once if (this->is_thrift) { //[prefix].thrift.h if (!this->generate_thrift_type_file(cur_info)) { fprintf(stderr, "[Generator Error] generate thrift type file failed.\n"); return false; } } for (const auto& desc : this->info.desc_list) { if (desc.block_type == "service") { //has_service = true; if (!this->generate_srpc_file(cur_info)) // [prefix].srpc.h { fprintf(stderr, "[Generator Error] generate srpc file failed.\n"); return false; } break; } } fprintf(stdout, "[Generator Done]\n"); return true; } void Generator::generate_skeleton(const std::string& idl_file) { const char *p; if (this->is_thrift) { p = idl_file.c_str() + idl_file.size() - 7; if (strncmp(p, ".thrift", 7) != 0) return; } else { p = idl_file.c_str() + idl_file.size() - 6; if (strncmp(p, ".proto", 6) != 0) return; } p = idl_file.c_str() + idl_file.size() - 1; while (p > idl_file.c_str()) { if (*p == '/') break; p--; } if (*p == '/') p++; std::string str = p; auto pos = str.find("."); if (pos == std::string::npos) return; std::string idl_file_name = str.substr(0, pos); // server.skeleton.cc this->generate_server_cpp_file(this->info, idl_file_name); // client.skeleton.cc this->generate_client_cpp_file(this->info, idl_file_name); fprintf(stdout, "[Generator] generate server files: %s, client files: %s\n", this->server_cpp_file.c_str(), this->client_cpp_file.c_str()); return; } void Generator::thrift_replace_include(const idl_info& cur_info, std::vector& params) { for (auto& param : params) { if (param.data_type == srpc::TDT_LIST || param.data_type == srpc::TDT_MAP || param.data_type == srpc::TDT_SET) { for (const auto& desc : cur_info.desc_list) { if (desc.block_type == "enum") SGenUtil::replace(param.type_name, desc.block_name, "int32_t"); } for (const auto& sub_info : cur_info.include_list) { for (const auto& desc : sub_info.desc_list) { if (desc.block_type == "enum") SGenUtil::replace(param.type_name, sub_info.file_name_prefix + desc.block_name, "int32_t"); } } } auto pos = param.type_name.find('.'); if (pos != std::string::npos) { for (const auto& sub_info : cur_info.include_list) { if (sub_info.package_name.empty()) continue; //printf("????%s %s\n", sub_info.file_name_prefix.c_str(), sub_info.package_name.c_str()); SGenUtil::replace(param.type_name, sub_info.file_name_prefix, "::" + join_package_names(sub_info.package_name) + "::"); } } } } bool Generator::generate_thrift_type_file(idl_info& cur_info) { if (!this->printer.open(this->thrift_type_file)) { fprintf(stderr, "[Generator Error] can't write to thirft_type_file: %s.\n", this->thrift_type_file.c_str()); return false; } this->printer.print_thrift_include(cur_info); this->printer.print_thrift_struct_declaration(cur_info); this->printer.print_thrift_typedef(cur_info); for (auto& desc : cur_info.desc_list) { if (desc.block_type == "service") { this->printer.print_service_namespace(desc.block_name); for (auto& rpc : desc.rpcs) { this->thrift_replace_include(cur_info, rpc.req_params); this->printer.print_rpc_thrift_struct_class(rpc.request_name, rpc.req_params,cur_info); this->thrift_replace_include(cur_info, rpc.resp_params); this->printer.print_rpc_thrift_struct_class(rpc.response_name, rpc.resp_params,cur_info); } this->printer.print_service_namespace_end(desc.block_name); } else if (desc.block_type == "struct" || desc.block_type == "union") { this->thrift_replace_include(cur_info, desc.st.params); this->printer.print_rpc_thrift_struct_class(desc.block_name, desc.st.params,cur_info); } else if (desc.block_type == "enum") { this->printer.print_thrift_include_enum(desc.block_name, desc.enum_lines); } } this->printer.print_end(cur_info.package_name); this->printer.close(); return true; } bool Generator::generate_srpc_file(const idl_info& cur_info) { if (!this->printer.open(this->srpc_file)) { fprintf(stderr, "[Generator Error] can't write to srpc file: %s.\n", this->srpc_file.c_str()); return false; } this->printer.print_srpc_include(this->prefix, cur_info.package_name); std::vector rpc_list; std::string package; if (this->is_thrift) { rpc_list.push_back("SRPC"); rpc_list.push_back("SRPCHttp"); rpc_list.push_back("Thrift"); rpc_list.push_back("ThriftHttp"); } else { rpc_list.push_back("SRPC"); rpc_list.push_back("SRPCHttp"); rpc_list.push_back("BRPC"); rpc_list.push_back("TRPC"); rpc_list.push_back("TRPCHttp"); } for (const std::string& p : cur_info.package_name) { package.append(p); package.push_back('.'); } for (const auto& desc : cur_info.desc_list) { if (desc.block_type != "service") continue; this->printer.print_service_namespace(desc.block_name); this->printer.print_server_comment(); if (this->is_thrift) this->printer.print_server_class_thrift(desc.block_name, desc.rpcs); else this->printer.print_server_class(desc.block_name, desc.rpcs); this->printer.print_client_comment(); for (const auto& rpc : desc.rpcs) { this->printer.print_client_define_done(rpc.method_name, rpc.response_name); } this->printer.print_empty_line(); for (const auto& type : rpc_list) this->printer.print_client_class(type, desc.block_name, desc.rpcs); this->printer.print_implement_comments(); this->printer.print_server_constructor(package + desc.block_name, desc.rpcs); if (this->is_thrift) this->printer.print_server_methods_thrift(desc.block_name, desc.rpcs); for (const auto& type : rpc_list) { this->printer.print_client_constructor(type, desc.block_name, cur_info.package_name); this->printer.print_client_methods(type, desc.block_name, desc.rpcs, cur_info.package_name); this->printer.print_client_create_task(type, desc.block_name, desc.rpcs, cur_info.package_name); } this->printer.print_service_namespace_end(desc.block_name); } this->printer.print_end(cur_info.package_name); this->printer.close(); return true; } bool Generator::generate_server_cpp_file(const idl_info& cur_info, const std::string& idl_file_name) { this->server_cpp_file = this->out_dir; this->server_cpp_file.append("server"); this->server_cpp_file.append(this->is_thrift ? ".thrift_skeleton." : ".pb_skeleton."); this->server_cpp_file.append("cc"); if (this->printer.open(this->server_cpp_file) == false) return false; this->printer.print_server_file_include(idl_file_name); for (const auto& desc : cur_info.desc_list) { if (desc.block_type != "service") continue; this->printer.print_server_class_impl(cur_info.package_name, desc.block_name); for (const auto& rpc : desc.rpcs) { this->printer.print_server_impl_method(cur_info.package_name, desc.block_name, rpc.method_name, rpc.request_name, rpc.response_name); } this->printer.print_server_class_impl_end(); } this->printer.print_server_main_begin(); this->printer.print_server_main_address(); for (const auto& desc : cur_info.desc_list) { if (desc.block_type != "service") continue; this->printer.print_server_main_method(desc.block_name); } this->printer.print_server_main_end(); this->printer.print_server_main_return(); this->printer.close(); return true; } bool Generator::generate_client_cpp_file(const idl_info& cur_info, const std::string& idl_file_name) { this->client_cpp_file = this->out_dir; this->client_cpp_file.append("client"); this->client_cpp_file.append(this->is_thrift ? ".thrift_skeleton." : ".pb_skeleton."); this->client_cpp_file.append("cc"); if (this->printer.open(this->client_cpp_file) == false) return false; this->printer.print_client_file_include(idl_file_name); for (const auto& desc : cur_info.desc_list) { if (desc.block_type != "service") continue; for (const auto& rpc : desc.rpcs) { this->printer.print_client_done_method(cur_info.package_name, desc.block_name, rpc.method_name, rpc.response_name); } } this->printer.print_client_main_begin(); this->printer.print_client_main_address(); int id = 0; for (const auto& desc : cur_info.desc_list) { if (desc.block_type != "service") continue; std::string suffix; if (id != 0) suffix = std::to_string(id); id++; if (this->is_thrift) this->printer.print_client_main_service("Thrift", cur_info.package_name, desc.block_name, suffix); else this->printer.print_client_main_service("SRPC", cur_info.package_name, desc.block_name, suffix); auto rpc_it = desc.rpcs.cbegin(); if (rpc_it != desc.rpcs.cend()) { this->printer.print_client_main_method_call(cur_info.package_name, desc.block_name, rpc_it->method_name, rpc_it->request_name, suffix); rpc_it++; //if (rpc_it == desc.rpcs.end()) // rpc_it = desc.rpcs.begin(); //this->printer.print_client_main_create_task(print_client_main_create_task, rpc_it->method_name, // rpc_it->request_name); } } this->printer.print_client_main_end(); this->printer.close(); return true; } sogou-srpc-ac65014/src/generator/generator.h000066400000000000000000000043411525217424400210640ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_GENERATOR_H__ #define __RPC_GENERATOR_H__ #include #include #include #include #include #include #include #include #include "printer.h" #include "parser.h" struct GeneratorParams { const char *out_dir; bool generate_skeleton; std::string idl_file; std::string input_dir; GeneratorParams() : out_dir(NULL), generate_skeleton(true) { } }; class Generator { public: Generator(bool is_thrift): parser(is_thrift), printer(is_thrift) { this->suffix = ".srpc."; this->thrift_suffix = ".thrift."; this->skeleton_suffix = ".skeleton."; this->is_thrift = is_thrift; } bool generate(struct GeneratorParams& params); protected: virtual bool generate_server_cpp_file(const idl_info& cur_info, const std::string& idle_file_name); virtual bool generate_client_cpp_file(const idl_info& cur_info, const std::string& idle_file_name); std::string server_cpp_file; std::string client_cpp_file; private: bool generate_header(idl_info& cur_info, struct GeneratorParams& params); void generate_skeleton(const std::string& idl_file); bool generate_srpc_file(const idl_info& cur_info); bool generate_thrift_type_file(idl_info& cur_info); void thrift_replace_include(const idl_info& cur_info, std::vector& params); bool init_file_names(const std::string& idl_file, const char *out_dir); Parser parser; Printer printer; idl_info info; std::string out_dir; std::string suffix; std::string thrift_suffix; std::string skeleton_suffix; std::string prefix; std::string srpc_file; std::string thrift_type_file; bool is_thrift; }; #endif sogou-srpc-ac65014/src/generator/parser.cc000066400000000000000000001044101525217424400205260ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include "parser.h" #include "thrift/rpc_thrift_enum.h" #define LINE_LENGTH_MAX 2048 static std::string gen_param_var(const std::string& type_name, size_t& cur, idl_info& info); std::string type_prefix_to_namespace(const std::string& type_name, idl_info& info); Descriptor *search_cur_file_descriptor(idl_info& info, const std::string& block_type, const std::string& block_name); Descriptor *search_include_file_descriptor(idl_info& info, const std::string include_file_name, const std::string& block_type, const std::string& block_name); idl_info *search_include_file(idl_info& info, const std::string file_name); idl_info *search_namespace(idl_info& info, const std::string name_space); void parse_thrift_type_name(const std::string& type_name, std::string& type_prefix, std::string& real_type_name); std::vector parse_thrift_variable(const std::string& str, char sep); bool Parser::parse(const std::string& proto_file, idl_info& info) { std::string idl_file = info.input_dir + proto_file; auto pos = idl_file.find_last_of('/'); if (pos == std::string::npos) info.file_name = idl_file; else info.file_name = idl_file.substr(pos + 1); pos = info.file_name.find_last_of('.'); if (pos == std::string::npos) info.file_name_prefix = info.file_name + "."; else info.file_name_prefix = info.file_name.substr(0, pos + 1); info.absolute_file_path = idl_file; FILE *in = fopen(idl_file.c_str(), "r"); if (!in) { fprintf(stderr, "[Parser] proto file: [%s] not exists.\n", idl_file.c_str()); return false; } fprintf(stdout, "proto file: [%s]\n", idl_file.c_str()); char line_buffer[LINE_LENGTH_MAX]; std::string file_path; std::string block; int state = PARSER_ST_OUTSIDE_BLOCK; bool flag_append; int stack_count = 0; std::string block_name; std::string block_type; std::string extends_type; std::string old_type_name; std::string new_type_name; std::string thrift_type_prefix; std::string thrift_type_name; bool succ; while (fgets(line_buffer, LINE_LENGTH_MAX, in)) { std::string line = SGenUtil::strip(line_buffer); if (line.empty()) continue; this->check_single_comments(line); if (line.empty()) continue; this->check_single_comments(line); if ((state & PARSER_ST_COMMENT_MASK) == PARSER_ST_INSIDE_COMMENT) { if (this->check_multi_comments_end(line)) { state -= PARSER_ST_INSIDE_COMMENT; // state |= PARSER_ST_OUTSIDE_COMMENT_MASK; if (line.empty()) continue; } else continue; } else if (this->check_multi_comments_begin(line)) { state = (state & PARSER_ST_OUTSIDE_COMMENT_MASK) + PARSER_ST_INSIDE_COMMENT; if (line.empty()) continue; } if (this->is_thrift == false) { int rpc_option = this->parse_pb_rpc_option(line); if (rpc_option == 1) continue; else if (rpc_option == 2) { fprintf(stderr, "[Parser ERROR] %s must not set " "\"option cc_generic_services = true\" for srpc.\n", idl_file.c_str()); return false; } } if (this->parse_package_name(line, info.package_name) == true) continue; if (this->parse_include_file(line, file_path) == true) { if (!this->is_thrift) { if (file_path.rfind("google/protobuf/", 0) == 0) continue; } info.include_list.resize(info.include_list.size() + 1); info.include_list.back().input_dir = info.input_dir; succ = this->parse(file_path, info.include_list.back()); if (!succ) { info.include_list.pop_back(); fprintf(stderr, "[Parser ERROR] failed to parse " "\" %s \" in \" %s \"\n", file_path.c_str(), idl_file.c_str()); return false; } continue; } if (this->is_thrift && this->parse_thrift_typedef(line, old_type_name, new_type_name, info) == true) { info.typedef_list.push_back(typedef_descriptor{old_type_name, new_type_name}); continue; } flag_append = false; if ((state & PARSER_ST_BLOCK_MASK) == PARSER_ST_OUTSIDE_BLOCK) { if (this->check_block_begin(in, line) == true) { state |= PARSER_ST_INSIDE_BLOCK; // stack_count++; block.clear(); block = line; if (this->parse_block_name(line, block_type, block_name, extends_type) == false) { fprintf(stderr, "Invalid proto file line: %s\n", line.c_str()); fprintf(stderr, "Failed to parse block name or value.\n"); return false; } } } else flag_append = true; // not else, because { }; can in the same line if ((state & PARSER_ST_BLOCK_MASK) == PARSER_ST_INSIDE_BLOCK) { if (flag_append == true) block.append(line); // block.append(line + "\n"); if (this->check_block_begin(line) == true) ++stack_count; if (this->check_block_end(line) == true) { --stack_count; if (stack_count == 0) { state = PARSER_ST_OUTSIDE_BLOCK; //state &= PARSER_ST_OUTSIDE_BLOCK_MASK; Descriptor desc; if (block_type == "service") { if (this->is_thrift) succ = this->parse_service_thrift(info.file_name_prefix, block, desc, info); else succ = this->parse_service_pb(block, desc); if (!succ) return false; desc.block_type = block_type; desc.block_name = block_name; desc.extends_type = extends_type; if (desc.extends_type != "" && this->is_thrift) { Descriptor *extended_desc = NULL; parse_thrift_type_name(desc.extends_type, thrift_type_prefix, thrift_type_name); if (thrift_type_prefix == "") extended_desc = search_cur_file_descriptor(info, "service", thrift_type_name); else extended_desc = search_include_file_descriptor(info, thrift_type_prefix+".thrift", "service", thrift_type_name); if (!extended_desc) { fprintf(stderr,"service %s extends type %s not found\n", desc.block_name.c_str(), desc.extends_type.c_str()); } else { desc.rpcs.insert(desc.rpcs.begin(), extended_desc->rpcs.begin(), extended_desc->rpcs.end()); } } info.desc_list.emplace_back(std::move(desc)); } else if ((block_type == "struct" || block_type == "union") && this->is_thrift) { succ = this->parse_struct_thrift(info.file_name_prefix, block, desc, info); if (!succ) return false; desc.block_type = block_type; desc.block_name = block_name; info.desc_list.emplace_back(std::move(desc)); } else if (block_type == "exception" && this->is_thrift) { succ = this->parse_struct_thrift(info.file_name_prefix, block, desc, info); if (!succ) return false; desc.block_type = "struct"; desc.block_name = block_name; info.desc_list.emplace_back(std::move(desc)); } else if (block_type == "enum" && this->is_thrift) { succ = this->parse_enum_thrift(block, desc); if (!succ) return false; SGenUtil::get_enum_set()->insert(info.file_name_prefix + block_name); desc.block_type = block_type; desc.block_name = block_name; info.desc_list.emplace_back(std::move(desc)); } } } } } build_typedef_mapping(info); fclose(in); fprintf(stdout, "finish parsing proto file: [%s]\n", idl_file.c_str()); return true; } std::string Parser::find_typedef_mapping_type(std::string& type_name, size_t& cur, idl_info& info) { size_t st = cur; cur = SGenUtil::find_next_nonspace(type_name,cur); for (; cur < type_name.size(); cur++) { if (type_name[cur] == ',' || type_name[cur] == '>' || type_name[cur] == '<') break; } auto idl_type = SGenUtil::strip(type_name.substr(st, cur - st)); if (info.typedef_mapping.find(idl_type) != info.typedef_mapping.end()) return info.typedef_mapping[idl_type]; if (idl_type == "bool" || idl_type == "int8_t" || idl_type == "int16_t" || idl_type == "int32_t" || idl_type == "int64_t" || idl_type == "uint64_t" || idl_type == "double" || idl_type == "std::string") { return idl_type; } else if (idl_type == "std::map" && cur < type_name.size() && type_name[cur] == '<') { auto key_type = find_typedef_mapping_type(type_name, ++cur, info); auto val_type = find_typedef_mapping_type(type_name, ++cur, info); ++cur; return "std::map<" + key_type + ", " + val_type + ">"; } else if (idl_type == "std::set" && cur < type_name.size() && type_name[cur] == '<') { auto val_type = find_typedef_mapping_type(type_name, ++cur, info); ++cur; return "std::set<" + val_type + ">"; } else if (idl_type == "std::vector" && cur < type_name.size() && type_name[cur] == '<') { auto val_type = find_typedef_mapping_type(type_name, ++cur, info); ++cur; return "std::vector<" + val_type + ">"; } size_t pos = idl_type.find("::",0); std::string real_type_name; std::string type_namespace; if (pos == std::string::npos) real_type_name = idl_type; else { real_type_name = idl_type.substr(pos+2); type_namespace = idl_type.substr(0,pos); } for (auto& include : info.include_list) { if ( (type_namespace != "" && include.package_name.size() > 0 && include.package_name[0] == type_namespace) || (type_namespace == "" && include.package_name.size() == 0) ) { for (auto& t : include.typedef_list) { if (real_type_name == t.new_type_name) { size_t offset = 0; include.typedef_mapping[real_type_name] = find_typedef_mapping_type(t.old_type_name, offset, include); return include.typedef_mapping[real_type_name]; } } } } for (auto& t : info.typedef_list) { if (real_type_name == t.new_type_name) { size_t offset = 0; info.typedef_mapping[real_type_name] = find_typedef_mapping_type(t.old_type_name, offset, info); return info.typedef_mapping[real_type_name]; } } return idl_type; } void Parser::build_typedef_mapping(idl_info& info) { for (auto &include:info.include_list) { for (auto &t:include.typedef_list) { if (include.typedef_mapping.find(t.new_type_name) != include.typedef_mapping.end()) continue; size_t cur = 0; include.typedef_mapping[t.new_type_name] = find_typedef_mapping_type(t.old_type_name, cur, include); } } for (auto &t:info.typedef_list) { if (info.typedef_mapping.find(t.new_type_name) != info.typedef_mapping.end()) continue; size_t cur = 0; info.typedef_mapping[t.new_type_name] = find_typedef_mapping_type(t.old_type_name, cur, info); } } // check / * and cut the first available line bool Parser::check_multi_comments_begin(std::string& line) { size_t pos = line.find("/*"); if (pos == std::string::npos) return false; line = line.substr(0, pos); return true; } // check * / and cut the rest available line bool Parser::check_multi_comments_end(std::string& line) { size_t pos = line.find("*/"); if (pos == std::string::npos) return false; pos += 2; while (line[pos] == ' ') pos++; line = line.substr(pos, line.length() - 1 - pos); return true; } // [ret] 0: no rpc option; 1: rpc_option = false; 2: rpc_option = true; int Parser::parse_pb_rpc_option(const std::string& line) { size_t pos = line.find("option"); if (pos == std::string::npos) return 0; pos = line.find("cc_generic_services", pos); if (pos == std::string::npos) return 0; pos = line.find("true", pos); if (pos == std::string::npos) return 1; return 2; } bool Parser::parse_thrift_typedef(const std::string& line, std::string& old_type_name, std::string& new_type_name, idl_info&info) { std::string str = line.substr(0, line.find_first_of(';')); std::vector elems = SGenUtil::split_by_space(str); if (elems.size() >= 3 && elems[0] == "typedef") { size_t offset = 0; std::string idl_type; for (size_t i = 1; i < elems.size()-1; i++) idl_type.append(elems[i]); old_type_name = gen_param_var(idl_type,offset,info); new_type_name = elems[elems.size()-1]; return true; } return false; } bool Parser::parse_include_file(const std::string& line, std::string& file_name) { std::string include_prefix = (this->is_thrift ? "include" : "import"); size_t pos = line.find(include_prefix); if (pos != 0) return false; auto st = line.find_first_of('\"'); auto ed = line.find_last_of('\"'); if (st == std::string::npos || ed == std::string::npos || st == ed) return false; file_name = line.substr(st + 1, ed - st - 1); // fprintf(stderr, "parse_include_file(%s,%s)\n", line.c_str(), file_name.c_str()); return true; } bool Parser::parse_package_name(const std::string& line, std::vector& package_name) { std::string package_prefix = (this->is_thrift ? "namespace" : "package"); size_t pos = line.find(package_prefix); if (pos != 0) return false; pos += package_prefix.length(); while (pos < line.length() && isspace(line[pos])) pos++; if (this->is_thrift) { pos = line.find("cpp", pos); if (pos == std::string::npos) return false; pos += 3; while (pos < line.length() && isspace(line[pos])) pos++; } size_t begin; if (this->is_thrift) { begin = line.find_last_of('/'); if (begin == std::string::npos) begin = pos; else begin++; } else { begin = pos; } while (pos < line.length() && !isspace(line[pos]) && line[pos] != ';') { pos++; } std::string names = line.substr(begin, pos - begin); pos = names.find('.'); while (pos != (size_t)-1) { package_name.push_back(names.substr(0, pos)); names = names.substr(pos + 1, names.length() - pos); pos = names.find('.'); } package_name.push_back(names.substr(0, pos)); return true; } static std::string gen_param_var(const std::string& type_name, size_t& cur, idl_info& info) { size_t st = cur; cur = SGenUtil::find_next_nonspace(type_name,cur); for (; cur < type_name.size(); cur++) { if (type_name[cur] == ',' || type_name[cur] == '>' || type_name[cur] == '<') break; } auto idl_type = SGenUtil::strip(type_name.substr(st, cur - st)); if (idl_type == "bool") return "bool"; else if (idl_type == "i8" || idl_type == "byte") return "int8_t"; else if (idl_type == "i16") return "int16_t"; else if (idl_type == "i32") return "int32_t"; else if (idl_type == "i64") return "int64_t"; else if (idl_type == "u64") return "uint64_t"; else if (idl_type == "double") return "double"; else if (idl_type == "string" || idl_type == "binary") return "std::string"; else if (idl_type == "map" && cur < type_name.size() && type_name[cur] == '<') { auto key_type = gen_param_var(type_name, ++cur, info); auto val_type = gen_param_var(type_name, ++cur, info); ++cur; return "std::map<" + key_type + ", " + val_type + ">"; } else if (idl_type == "set" && cur < type_name.size() && type_name[cur] == '<') { auto val_type = gen_param_var(type_name, ++cur, info); ++cur; return "std::set<" + val_type + ">"; } else if (idl_type == "list" && cur < type_name.size() && type_name[cur] == '<') { auto val_type = gen_param_var(type_name, ++cur, info); ++cur; return "std::vector<" + val_type + ">"; } return type_prefix_to_namespace(idl_type, info); } static void fill_rpc_param_type(const std::string& file_name_prefix, const std::string idl_type, rpc_param& param, idl_info& info) { if (idl_type == "bool") { param.data_type = srpc::TDT_BOOL; param.type_name = "bool"; } else if (idl_type == "i8" || idl_type == "byte") { param.data_type = srpc::TDT_I08; param.type_name = "int8_t"; } else if (idl_type == "i16") { param.data_type = srpc::TDT_I16; param.type_name = "int16_t"; } else if (idl_type == "i32") { param.data_type = srpc::TDT_I32; param.type_name = "int32_t"; } else if (idl_type == "i64") { param.data_type = srpc::TDT_I64; param.type_name = "int64_t"; } else if (idl_type == "u64") { param.data_type = srpc::TDT_U64; param.type_name = "uint64_t"; } else if (idl_type == "double") { param.data_type = srpc::TDT_DOUBLE; param.type_name = "double"; } else if (idl_type == "string" || idl_type == "binary") { param.data_type = srpc::TDT_STRING; param.type_name = "std::string"; } else if (SGenUtil::start_with(idl_type, "list")) { size_t cur = 0; param.data_type = srpc::TDT_LIST; param.type_name = gen_param_var(idl_type, cur, info); } else if (SGenUtil::start_with(idl_type, "map")) { size_t cur = 0; param.data_type = srpc::TDT_MAP; param.type_name = gen_param_var(idl_type, cur, info); } else if (SGenUtil::start_with(idl_type, "set")) { size_t cur = 0; param.data_type = srpc::TDT_SET; param.type_name = gen_param_var(idl_type, cur, info); } else { auto *enum_set = SGenUtil::get_enum_set(); if (enum_set->count(idl_type) > 0 || enum_set->count(file_name_prefix + idl_type) > 0) { //enum param.data_type = srpc::TDT_I32; param.type_name = "int32_t"; } else { //struct param.data_type = srpc::TDT_STRUCT; param.type_name = type_prefix_to_namespace(idl_type,info); } } } std::vector Parser::split_thrift_rpc(const std::string& str) { std::vector res; std::string::const_iterator start = str.begin(); std::string::const_iterator parameter_end = str.end(); std::string::const_iterator throws_end = str.end(); while (1) { parameter_end = find(start,str.end(), ')'); if (parameter_end == str.end()) { res.emplace_back(start,parameter_end); break; } std::string::const_iterator offset = find_if(parameter_end + 1, str.end(), [](char c){return !std::isspace(c);}); if (offset == str.end()) { res.emplace_back(start,parameter_end); break; } if (str.compare(offset-str.begin(), 6, "throws") == 0) { throws_end = find(offset,str.end(), ')'); res.emplace_back(start,throws_end); start = throws_end + 1; } else { res.emplace_back(start,parameter_end); start = parameter_end + 1; } } return res; } bool Parser::parse_rpc_param_thrift(const std::string& file_name_prefix, const std::string& str, std::vector& params, idl_info& info) { size_t left_b = 0; rpc_param param; std::string idl_type; if (left_b + 1 < str.size()) { auto bb = parse_thrift_variable(str.substr(left_b + 1), ','); for (const auto& ele : bb) { auto single_line = SGenUtil::split_skip_string(ele, '\n'); if (single_line.size() != 1) continue; auto filedparam = SGenUtil::split_skip_string(single_line[0], ':'); if (filedparam.size() != 2) continue; auto typevar = parse_thrift_variable(filedparam[1], ' '); if (typevar.size() == 3) // for optional/required in thrift2 { std::string qualifier = SGenUtil::strip(typevar[0]); if (qualifier == "optional") { param.required_state = srpc::THRIFT_STRUCT_FIELD_OPTIONAL; typevar.erase(typevar.begin()); } else if (qualifier == "required") { param.required_state = srpc::THRIFT_STRUCT_FIELD_REQUIRED; typevar.erase(typevar.begin()); } else continue; // error } else if (typevar.size() == 2) { param.required_state = srpc::THRIFT_STRUCT_FIELD_REQUIRED; } else continue; param.var_name = typevar[1]; param.field_id = atoi(SGenUtil::strip(filedparam[0]).c_str()); idl_type = SGenUtil::strip(typevar[0]); fill_rpc_param_type(file_name_prefix, idl_type, param, info); params.push_back(param); } } return true; } bool Parser::parse_service_thrift(const std::string& file_name_prefix, const std::string& block, Descriptor& desc, idl_info& info) { rpc_descriptor rpc_desc; auto st = block.find_first_of('{'); auto ed = block.find_last_of('}'); if (st == std::string::npos || ed == std::string::npos || st == ed) return false; std::string valid_block = block.substr(st + 1, ed - st - 1); auto arr = split_thrift_rpc(valid_block); for (const auto& ele : arr) { auto line = SGenUtil::strip(ele); size_t i = 0; for (; i < line.size(); i++) { if (line[i] != ';' && line[i] != ',' && !isspace(line[i])) break; } if (i == line.size()) continue; line = line.substr(i); if (line.empty()) continue; auto left_b = line.find('('); if (left_b == std::string::npos) continue; std::string idl_type; std::string tmp = SGenUtil::strip(line.substr(0, left_b)); auto sp_pos = tmp.find_last_of(" \t"); if (sp_pos != std::string::npos) { idl_type = SGenUtil::strip(tmp.substr(0, sp_pos)); rpc_desc.method_name = SGenUtil::strip(tmp.substr(sp_pos+1)); } else continue; rpc_desc.request_name = rpc_desc.method_name + "Request"; rpc_desc.response_name = rpc_desc.method_name + "Response"; rpc_param param; param.var_name = "result"; param.required_state = srpc::THRIFT_STRUCT_FIELD_REQUIRED; param.field_id = 0; if (idl_type != "void") { fill_rpc_param_type(file_name_prefix, idl_type, param, info); rpc_desc.resp_params.push_back(param); } auto right_b = line.find(')',left_b); if (right_b == std::string::npos) { parse_rpc_param_thrift(file_name_prefix, line.substr(left_b), rpc_desc.req_params,info); } else { parse_rpc_param_thrift(file_name_prefix, line.substr(left_b, right_b - left_b), rpc_desc.req_params, info); auto throws_start = line.find("throws", right_b); if (throws_start != std::string::npos) { left_b = line.find('(', throws_start + 6); parse_rpc_param_thrift(file_name_prefix, line.substr(left_b), rpc_desc.resp_params, info); } } fprintf(stdout, "Successfully parse method:%s req:%s resp:%s\n", rpc_desc.method_name.c_str(), rpc_desc.request_name.c_str(), rpc_desc.response_name.c_str()); desc.rpcs.emplace_back(std::move(rpc_desc)); } return true; } void Parser::check_single_comments(std::string& line) { size_t pos = line.find("#"); if (pos == std::string::npos) pos = line.find("//"); if (pos != std::string::npos) { line.resize(pos); return; } if (pos == std::string::npos) pos = line.find("/*"); size_t end; while (pos != std::string::npos) { end = line.find("*/", pos + 2); if (end == std::string::npos) return; // multi_comments can handle this, except for 'a/*\n' line.erase(pos, end - pos + 2); pos = line.find("/*", pos); } } bool Parser::parse_enum_thrift(const std::string& block, Descriptor& desc) { rpc_param param; auto st = block.find_first_of('{'); auto ed = block.find_last_of('}'); if (st == std::string::npos || ed == std::string::npos || st == ed) return false; std::string valid_block = block.substr(st + 1, ed - st - 1); for (size_t i = 0; i < valid_block.size(); i++) if (valid_block[i] == '\n' || valid_block[i] == '\r' || valid_block[i] == ',') valid_block[i] = ';'; auto arr = SGenUtil::split_skip_string(valid_block, ';'); for (const auto& ele : arr) { auto line = SGenUtil::strip(ele); if (line.empty()) continue; if (line.back() == ';' || line.back() == ',') line.resize(line.size() - 1); desc.enum_lines.push_back(line); } return true; } bool Parser::parse_struct_thrift(const std::string& file_name_prefix, const std::string& block, Descriptor& desc, idl_info& info) { auto st = block.find_first_of('{'); auto ed = block.find_last_of('}'); if (st == std::string::npos || ed == std::string::npos || st == ed) return false; std::string valid_block = block.substr(st + 1, ed - st - 1); int deep = 0; bool in_string = false; for (size_t i = 0; i < valid_block.size(); i++) { int c = valid_block[i]; if (c == '\n' || c == '\r') { valid_block[i] = ';'; in_string = false; } else if (c == ',' && !in_string && deep == 0) valid_block[i] = ';'; else if (c == '<' && !in_string) deep++; else if (c == '>' && !in_string) deep--; else if (c == '[' && !in_string) valid_block[i] = '{'; else if (c == ']' && !in_string) valid_block[i] = '}'; else if (c == '\"') in_string = !in_string; else if (in_string && c == '\\') { if (i + 1 < valid_block.size()) i++; } } auto arr = SGenUtil::split_skip_string(valid_block, ';'); for (const auto& ele : arr) { auto line = SGenUtil::strip(ele); if (line.empty()) continue; auto aabb = SGenUtil::split_skip_string(line, ':'); if (aabb.size() != 2) continue; auto aa = SGenUtil::strip(aabb[0]); if (aa.empty()) continue; rpc_param param; param.field_id = atoi(aa.c_str()); auto bb = SGenUtil::strip(aabb[1]); auto bbcc = SGenUtil::split_skip_string(bb, '='); if (bbcc.size() == 2) { bb = SGenUtil::strip(bbcc[0]); param.default_value = SGenUtil::strip(bbcc[1]); } auto idx1 = std::string::npos;//bb.find_first_of(' '); auto idx2 = std::string::npos;//bb.find_last_of(' '); for (size_t i = 0; i < bb.size(); i++) { if (isspace(bb[i])) { idx1 = i; break; } } for (size_t i = 0; i < bb.size(); i++) { if (isspace(bb[bb.size() - i - 1])) { idx2 = bb.size() - i - 1; break; } } if (idx1 == std::string::npos || idx2 == std::string::npos) continue; std::string b1 = SGenUtil::strip(bb.substr(0, idx1)); std::string b2, b3; if (idx1 == idx2 || (b1 != "required" && b1 != "optional")) { param.required_state = srpc::THRIFT_STRUCT_FIELD_DEFAULT; b1 = "default"; b2 = SGenUtil::strip(bb.substr(0, idx2)); b3 = SGenUtil::strip(bb.substr(idx2 + 1)); } else { param.required_state = (b1 == "required") ? srpc::THRIFT_STRUCT_FIELD_REQUIRED : srpc::THRIFT_STRUCT_FIELD_OPTIONAL; b2 = SGenUtil::strip(bb.substr(idx1 + 1, idx2 - idx1 - 1)); b3 = SGenUtil::strip(bb.substr(idx2 + 1)); } if (b1.empty() || b2.empty() || b3.empty()) continue; param.var_name = b3; fill_rpc_param_type(file_name_prefix, b2, param, info); fprintf(stdout, "Successfully parse struct param: %s %s %s\n", param.type_name.c_str(), param.var_name.c_str(), param.default_value.c_str()); desc.st.params.push_back(param); } return true; } bool Parser::parse_service_pb(const std::string& block, Descriptor& desc) { size_t pos = block.find("{"); if (pos == std::string::npos) return false; while (pos < block.length()) { rpc_descriptor rpc_desc; pos = block.find("rpc", pos); if (pos == std::string::npos) { if (desc.rpcs.size() == 0) { fprintf(stderr, "no \"rpc\" in service block [%s]\n", block.c_str()); return false; } else { return true; } } pos += strlen("rpc"); while (block[pos] == ' ' && pos < block.length()) pos++; if (pos == block.length()) return false; size_t method_name_pos = block.find("(", pos); if (method_name_pos == std::string::npos) { fprintf(stderr, "no method_name in service block [%s]\n", block.c_str()); return false; } rpc_desc.method_name = std::string(&block[pos], &block[method_name_pos]); rpc_desc.method_name.erase(rpc_desc.method_name.find_last_not_of(" ") + 1); pos = method_name_pos; while (block[pos] == ' ' && pos < block.length()) pos++; if (pos == block.length()) return false; size_t request_name_pos = block.find(")", pos + 1); if (request_name_pos == std::string::npos) { fprintf(stderr, "no request_name in service block [%s]\n", block.c_str()); return false; } rpc_desc.request_name = std::string(&block[pos + 1], &block[request_name_pos]); pos = block.find("returns", pos + 1); if (pos == std::string::npos) { fprintf(stderr, "no \"returns\" in service block [%s]\n", block.c_str()); return false; } while (block[pos] == ' ' && pos < block.length()) pos++; if (pos == block.length()) return true; size_t response_name_pos = block.find("(", pos + 1); size_t response_name_end = block.find(")", pos + 1); if (response_name_pos == std::string::npos || response_name_end == std::string::npos) { fprintf(stderr, "no response_name in service block [%s]\n", block.c_str()); return false; } rpc_desc.response_name = std::string(&block[response_name_pos + 1], &block[response_name_end]); fprintf(stdout, "Successfully parse method:%s req:%s resp:%s\n", rpc_desc.method_name.c_str(), rpc_desc.request_name.c_str(), rpc_desc.response_name.c_str()); desc.rpcs.emplace_back(std::move(rpc_desc)); pos = response_name_end; } return true; } bool Parser::parse_block_name(const std::string& line, std::string& block_name, std::string& block_name_value, std::string& extends_type) { size_t pos = line.find("{"); if (pos == std::string::npos) { fprintf(stderr, "failed to parse block name in %s\n",line.c_str()); return false; } std::vector elems = SGenUtil::split_by_space(line.substr(0,pos)); if (elems.size() == 2) { block_name = elems[0]; block_name_value = elems[1]; extends_type = ""; } else if (this->is_thrift && elems.size() == 4 && elems[0] == "service" && elems[2] == "extends") { block_name = elems[0]; block_name_value = elems[1]; extends_type = elems[3]; } else { fprintf(stderr, "failed to parse block name in %s\n", line.c_str()); return false; } fprintf(stdout, "Successfully parse service block [%s] : %s\n", block_name.c_str(), block_name_value.c_str()); return true; } /* bool Parser::parse_block_name(const std::string& line, std::string& block_name, std::string& block_name_value) { size_t pos = line.find_first_of(" ", 0); if (pos == std::string::npos) { fprintf(stderr, "failed to parse rpc name in %s\n", line.c_str()); return false; } block_name = std::string(&line[0], &line[pos]); size_t value_pos = line.find_first_of(" ", pos + 1); if (value_pos != std::string::npos) { block_name_value = std::string(&line[pos + 1], &line[value_pos]); } else { size_t end = line.find("{"); if (end == std::string::npos) return false; end--; while (line[end] == '\n' || line[end] == '\r' || line[end] == ' ') end --; // block_name_value = std::string(&line[pos + 1], &line[line.length() - 1]); block_name_value = std::string(&line[pos + 1], &line[end + 1]); } fprintf(stdout, "Successfully parse service block [%s] : %s\n", block_name.c_str(), block_name_value.c_str()); return true; } */ bool Parser::check_block_begin(FILE *in, std::string& line) { if (line.find(";") != std::string::npos) return false; if (line.find("{") != std::string::npos) return true; char line_buffer[LINE_LENGTH_MAX]; if (fgets(line_buffer, LINE_LENGTH_MAX, in)) { std::string next = SGenUtil::strip(line_buffer); if (next.find("{") != std::string::npos) { line.append(next.c_str()); return true; } } return false; } bool Parser::check_block_begin(const std::string& line) { if (line.find("{") == std::string::npos) return false; return true; } bool Parser::check_block_end(const std::string& line) { if (line.find("}") == std::string::npos && line.find("};") == std::string::npos) return false; return true; } void parse_thrift_type_name(const std::string& type_name, std::string& type_prefix, std::string& real_type_name) { size_t pos = type_name.find('.',0); if (pos == std::string::npos) { type_prefix = ""; real_type_name = SGenUtil::strip(type_name); } else { type_prefix = SGenUtil::strip(type_name.substr(0,pos)); real_type_name = SGenUtil::strip(type_name.substr(pos+1)); } } std::string type_prefix_to_namespace(const std::string& type_name, idl_info& info) { std::string prefix; std::string real_type; parse_thrift_type_name(type_name, prefix, real_type); if (prefix == "") return type_name; idl_info *include = search_include_file(info, prefix + ".thrift"); if (include == NULL) { fprintf(stderr,"cannot find type %s\n",type_name.c_str()); return type_name; } std::string namespc; for (const auto& name : include->package_name) namespc += name+"::"; if (namespc.empty()) namespc = "::"; return namespc+real_type; } Descriptor *search_cur_file_descriptor(idl_info& info, const std::string& block_type, const std::string& block_name) { for (auto &desc : info.desc_list) { if (desc.block_type == block_type && desc.block_name == block_name) return &desc; } return NULL; } Descriptor *search_include_file_descriptor(idl_info& info, const std::string include_file_name, const std::string& block_type, const std::string& block_name) { for (auto &include : info.include_list) { if (include.file_name == include_file_name) return search_cur_file_descriptor(include, block_type, block_name); } return NULL; } idl_info *search_include_file(idl_info& info, const std::string file_name) { for (auto &include : info.include_list) { if (include.file_name == file_name) return &include; } return NULL; } idl_info *search_namespace(idl_info& info, const std::string name_space) { if (name_space == "") return &info; for (auto &include : info.include_list) { if (include.package_name.size() > 0 && include.package_name[0] == name_space) return &include; } return NULL; } int Parser::find_valid(const std::string& line) { /* char *p = line.c_str(); char *q = p; while (q != p + line.length()) { if(*q != ' ' && *q != '\t') return pos; } */ return 0; } std::vector parse_thrift_variable(const std::string& str, char sep) { std::vector res; if (sep == '\"') return res; const char *cursor = str.c_str(); const char *start = cursor; bool in_map = false; std::string param; while (*cursor) { if (*cursor == '\"') { cursor = SGenUtil::skip_string(++cursor); if (!*cursor) break; } else if (*cursor == sep) { param = std::string(start, cursor); if (in_map == false && param.find("map") != std::string::npos && param.find("<") != std::string::npos) { in_map = true; cursor++; continue; } if (start < cursor) res.emplace_back(param); start = cursor + 1; in_map = false; } cursor++; } if (start < cursor) res.emplace_back(start, cursor); return res; } sogou-srpc-ac65014/src/generator/parser.h000066400000000000000000000057521525217424400204010ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_PARSER_H__ #define __RPC_PARSER_H__ #define PARSER_ST_BLOCK_MASK 0x01 #define PARSER_ST_OUTSIDE_BLOCK 0x00 #define PARSER_ST_INSIDE_BLOCK 0x01 #define PARSER_ST_OUTSIDE_BLOCK_MASK 0xFFFE #define PARSER_ST_COMMENT_MASK 0x10 #define PARSER_ST_INSIDE_COMMENT 0x10 #define PARSER_ST_OUTSIDE_COMMENT_MASK 0xFFFD #define PARSER_ST_INSIDE_COMMENT_MASK 0xFFFF #define DIRECTORY_LENGTH 2048 #include #include #include #include #include #include #include #include #include #include "descriptor.h" class Parser { public: bool parse(const std::string& proto_file, idl_info& info); std::string find_typedef_mapping_type(std::string& type_name, size_t& cur, idl_info& info); void build_typedef_mapping(idl_info& info); int find_valid(const std::string& line); bool check_block_begin(FILE *in, std::string& line); bool check_block_begin(const std::string& line); bool check_block_end(const std::string& line); void check_single_comments(std::string& line); bool parse_block_name(const std::string& line, std::string& block_name, std::string& block_name_value, std::string& extend_type); bool parse_service_pb(const std::string& block, Descriptor& desc); std::vector split_thrift_rpc(const std::string &str); bool parse_thrift_typedef(const std::string& line, std::string& old_type_name, std::string& new_type_name, idl_info& info); bool parse_rpc_param_thrift(const std::string& file_name_prefix, const std::string &str, std::vector ¶ms, idl_info& info); bool parse_service_thrift(const std::string& file_name_prefix, const std::string& block, Descriptor& desc, idl_info& info); bool parse_struct_thrift(const std::string& file_name_prefix, const std::string& block, Descriptor& desc, idl_info& info); bool parse_enum_thrift(const std::string& block, Descriptor& desc); bool parse_package_name(const std::string& line, std::vector& package_name); bool parse_include_file(const std::string& line, std::string& file_name); bool check_multi_comments_begin(std::string& line); bool check_multi_comments_end(std::string& line); int parse_pb_rpc_option(const std::string& line); Parser(bool is_thrift) { this->is_thrift = is_thrift; } private: bool is_thrift; }; #endif sogou-srpc-ac65014/src/generator/printer.h000066400000000000000000001400531525217424400205620ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_PRINTER_H__ #define __RPC_PRINTER_H__ #include #include #include #include #include "descriptor.h" #include "thrift/rpc_thrift_enum.h" static inline std::string join_package_names(const std::vector& package) { std::string ret = package[0]; for (size_t i = 1; i < package.size(); i++) { ret.append("::"); ret.append(package[i]); } return ret; } static inline std::string change_include_prefix(const std::string& param) { size_t pos = param.find('.'); if (pos == std::string::npos) return param; std::string ret = param.substr(0, pos); ret.append("::"); ret.append(param.substr(pos + 1, param.length() - 1 - pos)); return ret; } static inline std::string make_thrift_package_prefix(const std::vector& package, const std::string& service, const std::string& param) { if (package.size() == 0) return service + "::" + param; std::string package_str = join_package_names(package); return "::" + package_str + "::" + service + "::" + param; } static inline std::string make_package_prefix(const std::vector& package, const std::string& param) { size_t pos = param.find('.'); if (pos != std::string::npos) return change_include_prefix(param); if (package.size() == 0) return param; std::string package_str = join_package_names(package); return "::" + package_str + "::" + param; } static inline std::string make_srpc_service_prefix(const std::vector& package, const std::string& service, char sp) { std::string name; for (const std::string& p : package) { name.append(p); name.push_back(sp); } name.append(service); return name; } static inline std::string make_trpc_service_prefix(const std::vector& package, const std::string& service) { if (package.size() == 0) return service; std::string package_prefix = package[0] + "."; for (size_t i = 1; i < package.size(); i++) package_prefix = package_prefix + package[i] + "."; return package_prefix + service; } static inline std::string make_trpc_method_prefix(const std::vector& package, const std::string& service, const std::string& method) { std::string method_prefix = "/"; for (size_t i = 0; i < package.size(); i++) method_prefix = method_prefix + package[i] + "."; return method_prefix + service + "/" + method; } static inline bool is_simple_type(int8_t data_type) { return data_type == srpc::TDT_BOOL || data_type == srpc::TDT_I08 || data_type == srpc::TDT_I16 || data_type == srpc::TDT_I32 || data_type == srpc::TDT_I64 || data_type == srpc::TDT_U64 || data_type == srpc::TDT_DOUBLE; } static inline void fill_thrift_sync_params(const rpc_descriptor& rpc, std::string& return_type, std::string& handler_params, std::string& send_params) { return_type = "void"; handler_params.clear(); send_params.clear(); if (!rpc.resp_params.empty()) { const auto ¶m = rpc.resp_params[0]; if (param.field_id == 0) { if (is_simple_type(param.data_type)) return_type = param.type_name; else { handler_params += param.type_name; handler_params += "& _return"; } } } for (const auto ¶m : rpc.req_params) { if (!handler_params.empty()) handler_params += ", "; if (!send_params.empty()) send_params += ", "; handler_params += "const "; handler_params += param.type_name; send_params += "const "; send_params += param.type_name; if (!is_simple_type(param.data_type)) { handler_params += "&"; send_params += "&"; } handler_params += " "; handler_params += param.var_name; send_params += " "; send_params += param.var_name; } } static inline void fill_thrift_async_params(const rpc_descriptor& rpc, std::string& return_type, std::string& handler_params) { return_type.clear(); handler_params.clear(); if (!rpc.resp_params.empty()) { const auto ¶m = rpc.resp_params[0]; if (param.field_id == 0) { if (is_simple_type(param.data_type)) return_type += "response->result = "; else handler_params += "response->result"; } } for (const auto ¶m : rpc.req_params) { if (!handler_params.empty()) handler_params += ", "; handler_params += "request->"; handler_params += param.var_name; } } static void output_descriptor(int& i, FILE *f, const std::string& type_name, size_t& cur, const idl_info& info) { int8_t data_type; size_t st = cur; std::string key_arg = "void"; std::string val_arg = "void"; for (; cur < type_name.size(); cur++) { if (type_name[cur] == ',' || type_name[cur] == '>' || type_name[cur] == '<') break; } auto cpptype = SGenUtil::strip(type_name.substr(st, cur - st)); if (info.typedef_mapping.find(cpptype) != info.typedef_mapping.end()) { size_t offset = 0; output_descriptor(i,f,info.typedef_mapping.at(cpptype),offset,info); return; } std::string ret; if (cpptype == "bool") data_type = srpc::TDT_BOOL; else if (cpptype == "int8_t") data_type = srpc::TDT_I08; else if (cpptype == "int16_t") data_type = srpc::TDT_I16; else if (cpptype == "int32_t") data_type = srpc::TDT_I32; else if (cpptype == "int64_t") data_type = srpc::TDT_I64; else if (cpptype == "uint64_t") data_type = srpc::TDT_U64; else if (cpptype == "double") data_type = srpc::TDT_DOUBLE; else if (cpptype == "std::string") data_type = srpc::TDT_STRING; else if (cpptype == "std::map" && cur < type_name.size() && type_name[cur] == '<') { data_type = srpc::TDT_MAP; output_descriptor(i, f, type_name, ++cur, info); key_arg = "subtype_" + std::to_string(i); output_descriptor(i, f, type_name, ++cur, info); val_arg = "subtype_" + std::to_string(i); cpptype = SGenUtil::strip(type_name.substr(st, ++cur - st)); } else if (cpptype == "std::set" && cur < type_name.size() && type_name[cur] == '<') { data_type = srpc::TDT_SET; output_descriptor(i, f, type_name, ++cur, info); val_arg = "subtype_" + std::to_string(i); cpptype = SGenUtil::strip(type_name.substr(st, ++cur - st)); } else if (cpptype == "std::vector" && cur < type_name.size() && type_name[cur] == '<') { data_type = srpc::TDT_LIST; output_descriptor(i, f, type_name, ++cur, info); val_arg = "subtype_" + std::to_string(i); cpptype = SGenUtil::strip(type_name.substr(st, ++cur - st)); } else data_type = srpc::TDT_STRUCT; fprintf(f, "\t\tusing subtype_%d = srpc::ThriftDescriptorImpl<%s, %d, %s, %s>;\n", ++i, cpptype.c_str(), data_type, key_arg.c_str(), val_arg.c_str()); } class Printer { public: bool open(std::string out_file) { this->out_file = fopen(out_file.c_str(), "w"); return this->out_file; } void close() { fclose(this->out_file); } void print_thrift_include(const idl_info& cur_info) { fprintf(this->out_file, "%s", this->thrift_include_format.c_str()); for (const auto& sub_info : cur_info.include_list) fprintf(this->out_file, "#include \"%s.h\"\n", sub_info.file_name.c_str()); //if (cur_info.package_name.length()) for (size_t i = 0; i < cur_info.package_name.size(); i++) { fprintf(this->out_file, this->namespace_package_start_format.c_str(), cur_info.package_name[i].c_str()); } } void print_thrift_struct_declaration(const idl_info& cur_info) { fprintf(this->out_file,"\n"); for (const auto& desc : cur_info.desc_list) { if (desc.block_type == "struct") fprintf(this->out_file,"class %s;\n",desc.block_name.c_str()); } fprintf(this->out_file,"\n"); } void print_thrift_typedef(const idl_info& cur_info) { fprintf(this->out_file,"\n"); for (const auto& t : cur_info.typedef_list) { fprintf(this->out_file,"typedef %s %s;\n",t.old_type_name.c_str(),t.new_type_name.c_str()); } fprintf(this->out_file,"\n"); } void print_thrift_include_enum(const std::string& name, const std::vector& enum_lines) { fprintf(this->out_file, "\nenum %s {\n", name.c_str()); for (const auto& line : enum_lines) fprintf(this->out_file, "\t%s,\n", line.c_str()); fprintf(this->out_file, "};\n"); } void print_rpc_thrift_struct_class(const std::string& class_name, const std::vector& class_params,const idl_info& info) { fprintf(this->out_file, thrift_struct_class_begin_format.c_str(), class_name.c_str()); for (const auto& ele : class_params) { if (ele.default_value.empty()) fprintf(this->out_file, "\t%s %s;\n", ele.type_name.c_str(), ele.var_name.c_str()); else fprintf(this->out_file, "\t%s %s = %s;\n", ele.type_name.c_str(), ele.var_name.c_str(), ele.default_value.c_str()); } fprintf(this->out_file, "%s", thrift_struct_class_isset_begin_format.c_str()); for (const auto& ele : class_params) { if (ele.required_state != srpc::THRIFT_STRUCT_FIELD_REQUIRED) { if (!ele.default_value.empty()) fprintf(this->out_file, "\t\tbool %s = true;\n", ele.var_name.c_str()); else fprintf(this->out_file, "\t\tbool %s = false;\n", ele.var_name.c_str()); } } fprintf(this->out_file, thrift_struct_class_isset_end_format.c_str(), class_name.c_str()); for (const auto& ele : class_params) { std::string type = ele.type_name; if (!is_simple_type(ele.data_type)) type += '&'; if (ele.required_state == srpc::THRIFT_STRUCT_FIELD_REQUIRED) fprintf(this->out_file, thrift_struct_class_set_required_format.c_str(), ele.var_name.c_str(), type.c_str(), ele.var_name.c_str()); else fprintf(this->out_file, thrift_struct_class_set_optional_format.c_str(), ele.var_name.c_str(), type.c_str(), ele.var_name.c_str(), ele.var_name.c_str()); } fprintf(this->out_file, thrift_struct_class_operator_equal_begin_format.c_str(), class_name.c_str()); for (const auto& ele : class_params) { if (ele.required_state == srpc::THRIFT_STRUCT_FIELD_REQUIRED) fprintf(this->out_file, thrift_struct_class_operator_equal_required_format.c_str(), ele.var_name.c_str(), ele.var_name.c_str()); else fprintf(this->out_file, thrift_struct_class_operator_equal_optional_format.c_str(), ele.var_name.c_str(), ele.var_name.c_str(), ele.var_name.c_str(), ele.var_name.c_str(), ele.var_name.c_str()); } fprintf(this->out_file, "%s", thrift_struct_class_operator_equal_end_format.c_str()); fprintf(this->out_file, thrift_struct_class_elements_start_format.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str(), class_name.c_str()); for (const auto& ele : class_params) { if (is_simple_type(ele.data_type)) { if (ele.default_value.empty()) fprintf(this->out_file, "\t\tthis->%s = 0;\n", ele.var_name.c_str()); } } fprintf(this->out_file, thrift_struct_class_constructor_end_format.c_str(), class_name.c_str(), class_name.c_str()); fprintf(this->out_file, thrift_struct_element_impl_begin_format.c_str(), class_name.c_str(), class_name.c_str()); int i = 0; for (const auto& ele : class_params) { size_t cur = 0; output_descriptor(i, this->out_file, ele.type_name, cur, info); std::string p_isset = "0"; if (ele.required_state != srpc::THRIFT_STRUCT_FIELD_REQUIRED) p_isset = "(const char *)(&st->__isset." + ele.var_name + ") - base"; fprintf(this->out_file, "\t\telements->push_back({subtype_%d::get_instance(), \"%s\", %s, (const char *)(&st->%s) - base, %d, %d});\n", i, ele.var_name.c_str(), p_isset.c_str(), ele.var_name.c_str(), ele.field_id, ele.required_state); } fprintf(this->out_file, thrift_struct_class_end_format.c_str(), class_name.c_str()); } void print_srpc_include(const std::string& prefix, const std::vector& package) { fprintf(this->out_file, this->srpc_include_format.c_str(), prefix.c_str(), this->is_thrift ? "thrift" : "pb"); for (size_t i = 0; i < package.size(); i++) fprintf(this->out_file, this->namespace_package_start_format.c_str(), package[i].c_str()); } void print_server_file_include(const std::string& prefix) { fprintf(this->out_file, this->srpc_file_include_format.c_str(), prefix.c_str()); fprintf(this->out_file, "%s", this->using_namespace_sogou_format.c_str()); fprintf(this->out_file, "%s", this->wait_group_declaration_format.c_str()); } void print_client_file_include(const std::string& prefix) { fprintf(this->out_file, this->srpc_file_include_format.c_str(), prefix.c_str()); fprintf(this->out_file, "%s", this->using_namespace_sogou_format.c_str()); fprintf(this->out_file, "%s", this->wait_group_declaration_format.c_str()); } void print_server_comment() { fprintf(this->out_file, "%s", this->server_comment_format.c_str()); } void print_client_comment() { fprintf(this->out_file, "%s", this->client_comment_format.c_str()); } void print_client_define_done(const std::string& method, const std::string& response) { std::string resp = change_include_prefix(response); fprintf(this->out_file, client_define_done_format.c_str(), method.c_str(), resp.c_str()); } /* void print_client_define_task(const std::string& package, const std::string& method, const std::string& request, const std::string& response) { std::string req = make_package_prefix(package, request); std::string resp = make_package_prefix(package, response); fprintf(this->out_file, client_define_task_format.c_str(), method.c_str(), req.c_str(), resp.c_str()); } void print_server_method(const std::string& package, const std::string& method, const std::string& request, const std::string& response, const std::string& service) { std::string req = make_package_prefix(package, request); std::string resp = make_package_prefix(package, response); fprintf(this->out_file, this->server_method_format.c_str(), method.c_str(), req.c_str(), resp.c_str(), service.c_str(), method.c_str(), req.c_str(), resp.c_str(), method.c_str()); } */ void print_server_class_impl(const std::vector& package, const std::string& service) { std::string base_service = make_package_prefix(package, service); fprintf(this->out_file, this->print_server_class_impl_format.c_str(), service.c_str(), base_service.c_str()); } void print_server_impl_method(const std::vector& package, const std::string& service, const std::string& method, const std::string& request, const std::string& response) { std::string req; std::string resp; if (this->is_thrift) { req = make_thrift_package_prefix(package, service, request); resp = make_thrift_package_prefix(package, service, response); } else { req = make_package_prefix(package, request); resp = make_package_prefix(package, response); } fprintf(this->out_file, this->server_impl_method_format.c_str(), method.c_str(), req.c_str(), resp.c_str()); } void print_server_class_impl_end() { fprintf(this->out_file, "};"); } void print_server_main_begin() { fprintf(this->out_file, "%s", this->server_main_begin_format.c_str()); if (!this->is_thrift) fprintf(this->out_file, "%s", this->main_pb_version_format.c_str()); } void print_server_main_address() { if (this->is_thrift) fprintf(this->out_file, this->server_main_ip_port_format.c_str(), "Thrift"); else fprintf(this->out_file, this->server_main_ip_port_format.c_str(), "SRPC"); } void print_server_main_method(const std::string& service) { std::string service_lower = service; std::transform(service_lower.begin(), service_lower.end(), service_lower.begin(), ::tolower); fprintf(this->out_file, this->server_main_method_format.c_str(), service.c_str(), service_lower.c_str(), service_lower.c_str()); } void print_server_main_end() { fprintf(this->out_file, "%s", this->server_main_end_format.c_str()); } void print_server_main_return() { if (!this->is_thrift) fprintf(this->out_file, "%s", this->main_pb_shutdown_format.c_str()); fprintf(this->out_file, "%s", this->main_end_return_format.c_str()); } void print_server_class_thrift(const std::string& service, const std::vector& rpcs) { std::string return_type; std::string handler_params; std::string send_params; fprintf(this->out_file, "%s", this->server_class_constructor_format.c_str()); for (const auto& rpc : rpcs) { fill_thrift_sync_params(rpc, return_type, handler_params, send_params); fprintf(this->out_file, this->server_class_method_declaration_thrift_handler_format.c_str(), return_type.c_str(), rpc.method_name.c_str(), handler_params.c_str()); } fprintf(this->out_file, "\npublic:\n"); for (const auto& rpc : rpcs) { fill_thrift_async_params(rpc, return_type, handler_params); fprintf(this->out_file, this->server_class_method_declaration_thrift_format.c_str(), rpc.method_name.c_str(), rpc.request_name.c_str(), rpc.response_name.c_str()); } fprintf(this->out_file, "%s", this->server_class_end_format.c_str()); } void print_server_methods_thrift(const std::string& service, const std::vector& rpcs) { std::string return_type; std::string handler_params; std::string send_params; for (const auto& rpc : rpcs) { fill_thrift_sync_params(rpc, return_type, handler_params, send_params); fprintf(this->out_file, this->server_class_method_implementation_thrift_handler_format.c_str(), return_type.c_str(), rpc.method_name.c_str(), handler_params.c_str(), return_type == "void" ? " " : " return 0; "); } for (const auto& rpc : rpcs) { fill_thrift_async_params(rpc, return_type, handler_params); fprintf(this->out_file, this->server_class_method_implementation_thrift_format.c_str(), rpc.method_name.c_str(), rpc.request_name.c_str(), rpc.response_name.c_str(), return_type.c_str(), rpc.method_name.c_str(), handler_params.c_str()); } } void print_server_class(const std::string& service, const std::vector& rpcs) { fprintf(this->out_file, "%s", this->server_class_constructor_format.c_str()); for (const auto& rpc : rpcs) { std::string req = change_include_prefix(rpc.request_name); std::string resp = change_include_prefix(rpc.response_name); fprintf(this->out_file, this->server_class_method_format.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str()); } fprintf(this->out_file, "%s", this->server_class_end_format.c_str()); } void print_server_methods(const std::string& service, const std::vector& rpcs) { for (const auto& rpc : rpcs) { fprintf(this->out_file, this->server_methods_format.c_str(), service.c_str(), service.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str()); } } void print_client_class(const std::string& type, const std::string& service, const std::vector& rpcs) { fprintf(this->out_file, this->client_class_constructor_start_format.c_str(), type.c_str(), type.c_str()); if (this->is_thrift) { std::string return_type; std::string handler_params; std::string send_params; std::string recv_params; for (const auto& rpc : rpcs) { fill_thrift_sync_params(rpc, return_type, handler_params, send_params); if (return_type != "void") recv_params.clear(); else if (!rpc.resp_params.empty() && rpc.resp_params[0].field_id == 0) recv_params = rpc.resp_params[0].type_name + "& _return"; else recv_params.clear(); fprintf(this->out_file, this->client_class_construct_methods_thrift_format.c_str(), return_type.c_str(), rpc.method_name.c_str(), handler_params.c_str(), rpc.method_name.c_str(), send_params.c_str(), return_type.c_str(), rpc.method_name.c_str(), recv_params.c_str()); } fprintf(this->out_file, "%s", this->client_class_get_last_thrift_format.c_str()); fprintf(this->out_file, "public:\n"); } for (const auto& rpc : rpcs) { std::string req = change_include_prefix(rpc.request_name); std::string resp = change_include_prefix(rpc.response_name); fprintf(this->out_file, this->client_class_construct_methods_format.c_str(), rpc.method_name.c_str(), req.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), resp.c_str(), rpc.method_name.c_str(), req.c_str()); } fprintf(this->out_file, this->client_class_constructor_format.c_str(), type.c_str(), type.c_str()); for (const auto& rpc : rpcs) { fprintf(this->out_file, this->client_class_create_methods_format.c_str(), type.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str()); //type.c_str(), rpc.method_name.c_str(), rpc.request_name.c_str(), rpc.method_name.c_str()); } if (this->is_thrift) { fprintf(this->out_file, "%s", client_class_private_get_thrift_format.c_str()); for (const auto& rpc : rpcs) { std::string resp = change_include_prefix(rpc.response_name); fprintf(this->out_file, this->client_class_get_rpc_thread_resp_thrift_format.c_str(), resp.c_str(), rpc.method_name.c_str()); } } fprintf(this->out_file, this->client_class_constructor_end_format.c_str(), service.c_str()); } void print_implement_comments() { fprintf(this->out_file, "\n///// implements detials /////\n"); } void print_server_constructor(const std::string& service, const std::vector& rpcs) { fprintf(this->out_file, this->server_constructor_method_format.c_str(), service.c_str()); for (const auto& rpc : rpcs) { fprintf(this->out_file, this->server_constructor_add_method_format.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str()); } fprintf(this->out_file, "}\n"); } void print_client_constructor(const std::string& type, const std::string& service, const std::vector& package) { bool is_srpc_thrift = (this->is_thrift && (type == "SRPC" || type == "SRPCHttp")); const char *method_ip = is_srpc_thrift ? client_constructor_methods_ip_srpc_thrift_format.c_str() : ""; const char *method_params = is_srpc_thrift ? client_constructor_methods_params_srpc_thrift_format.c_str() : ""; std::string full_service = service; if (type == "TRPC") full_service = make_trpc_service_prefix(package, service); else if (type == "SRPC" || type == "BRPC" || type == "TRPCHttp") full_service = make_srpc_service_prefix(package, service, '.'); else if (type == "SRPCHttp") full_service = make_srpc_service_prefix(package, service, '/'); fprintf(this->out_file, this->client_constructor_methods_format.c_str(), type.c_str(), type.c_str(), type.c_str(), full_service.c_str(), method_ip, type.c_str(), type.c_str(), type.c_str(), type.c_str(), full_service.c_str(), method_params, type.c_str()); } void print_client_methods(const std::string& type, const std::string& service, const std::vector& rpcs, const std::vector& package) { for (const auto& rpc : rpcs) { std::string req = change_include_prefix(rpc.request_name); std::string resp = change_include_prefix(rpc.response_name); if (type == "TRPC") { std::string full_method = make_trpc_method_prefix(package, service, rpc.method_name); fprintf(this->out_file, this->client_method_trpc_format.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), rpc.method_name.c_str(), full_method.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), resp.c_str(), full_method.c_str(), resp.c_str()); } else if (type == "TRPCHttp") { fprintf(this->out_file, this->client_method_trpc_format.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str()); } else { fprintf(this->out_file, this->client_method_format.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str(), type.c_str(), rpc.method_name.c_str(), req.c_str(), resp.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str()); } } if (this->is_thrift) { std::string return_type; std::string handler_params; std::string send_params; std::string recv_params; for (const auto& rpc : rpcs) { std::string req = change_include_prefix(rpc.request_name); std::string resp = change_include_prefix(rpc.response_name); std::string last_return; fill_thrift_sync_params(rpc, return_type, handler_params, send_params); if (return_type != "void") last_return = "return __thrift__sync__resp.result"; else if (!rpc.resp_params.empty() && rpc.resp_params[0].field_id == 0) last_return = "_return = std::move(__thrift__sync__resp.result)"; else last_return = "(void)__thrift__sync__resp"; fprintf(this->out_file, this->client_method_thrift_begin_format.c_str(), resp.c_str(), type.c_str(), rpc.method_name.c_str(), resp.c_str(), return_type.c_str(), type.c_str(), rpc.method_name.c_str(), handler_params.c_str(), req.c_str(), resp.c_str()); for (const auto ¶m : rpc.req_params) fprintf(this->out_file, "\t__thrift__sync__req.%s = %s;\n", param.var_name.c_str(), param.var_name.c_str()); fprintf(this->out_file, this->client_method_thrift_end_format.c_str(), rpc.method_name.c_str(), type.c_str(), last_return.c_str(), type.c_str(), rpc.method_name.c_str(), send_params.c_str(), req.c_str()); for (const auto ¶m : rpc.req_params) fprintf(this->out_file, "\t__thrift__sync__req.%s = %s;\n", param.var_name.c_str(), param.var_name.c_str()); if (return_type != "void") { last_return = "return std::move(res.result);"; recv_params.clear(); } else if (!rpc.resp_params.empty() && rpc.resp_params[0].field_id == 0) { last_return = "_return = std::move(res.result)"; recv_params = rpc.resp_params[0].type_name + "& _return"; } else { last_return = "(void)res"; recv_params.clear(); } fprintf(this->out_file, this->cilent_method_thrift_send_end_format.c_str(), resp.c_str(), rpc.method_name.c_str(), resp.c_str(), rpc.method_name.c_str(), return_type.c_str(), type.c_str(), rpc.method_name.c_str(), recv_params.c_str(), rpc.method_name.c_str(), last_return.c_str()); } fprintf(this->out_file, this->client_method_get_last_thrift_format.c_str(), type.c_str(), type.c_str(), type.c_str()); } } void print_client_create_task(const std::string& type, const std::string& service, const std::vector& rpcs, const std::vector& package) { for (const auto& rpc : rpcs) { if (type == "TRPC") { std::string full_method = make_trpc_method_prefix(package, service, rpc.method_name); fprintf(this->out_file, this->client_create_task_trpc_format.c_str(), type.c_str(), type.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), full_method.c_str()); } else if (type == "TRPCHttp") { fprintf(this->out_file, this->client_create_task_trpc_format.c_str(), type.c_str(), type.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str()); } else { fprintf(this->out_file, this->client_create_task_format.c_str(), type.c_str(), type.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str(), rpc.method_name.c_str()); } } } void print_service_namespace(const std::string& service) { fprintf(this->out_file, this->namespace_service_start_format.c_str(), service.c_str()); } void print_service_namespace_end(const std::string& service) { fprintf(this->out_file, this->namespace_service_end_format.c_str(), service.c_str()); } void print_create_client_controller(const std::string& service, const std::vector& rpcs) { for (const auto& rpc : rpcs) { this->print_create_method_controller(service, rpc.method_name); } } void print_create_method_controller(const std::string& service, const std::string& method) { std::string method_lower = method; std::transform(method_lower.begin(), method_lower.end(), method_lower.begin(), ::tolower); fprintf(this->out_file, this->namespace_method_task_format.c_str(), //create_method_cntl(stub) method.c_str(), method_lower.c_str(), service.c_str(), method.c_str(), method.c_str(), method.c_str()); } void print_client_done_method(const std::vector& package, const std::string& service, const std::string& method, const std::string& response) { std::string method_lower = method; std::transform(method_lower.begin(), method_lower.end(), method_lower.begin(), ::tolower); std::string resp; if (this->is_thrift) resp = make_thrift_package_prefix(package, service, response); else resp = make_package_prefix(package, response); fprintf(this->out_file, this->client_done_format.c_str(), method_lower.c_str(), resp.c_str(), service.c_str(), method.c_str()); } void print_client_main_begin() { fprintf(this->out_file, "%s", this->client_main_begin_format.c_str()); if (!this->is_thrift) fprintf(this->out_file, "%s", this->main_pb_version_format.c_str()); } void print_client_main_address() { fprintf(this->out_file, "%s", this->client_main_ip_port_format.c_str()); } void print_client_main_service(const std::string& type, const std::vector& package, const std::string& service, const std::string& suffix) { //std::string service_lower = service; //std::transform(service_lower.begin(), service_lower.end(), // service_lower.begin(), ::tolower); std::string base_service = make_package_prefix(package, service); fprintf(this->out_file, this->client_main_service_format.c_str(), base_service.c_str(), type.c_str(), suffix.c_str()); } void print_client_main_method_call(const std::vector& package, const std::string& service, const std::string& method, const std::string& request, const std::string& suffix) { std::string method_lower = method; std::transform(method_lower.begin(), method_lower.end(), method_lower.begin(), ::tolower); std::string req; if (this->is_thrift) req = make_thrift_package_prefix(package, service, request); else req = make_package_prefix(package, request); fprintf(this->out_file, this->client_main_method_call_format.c_str(), req.c_str(), method_lower.c_str(), method_lower.c_str(), suffix.c_str(), method.c_str(), method_lower.c_str(), method_lower.c_str()); } /* void print_client_main_create_task(const std::string& package, const std::string& service, const std::string& method, const std::string& request) { std::string method_lower = method; std::transform(method_lower.begin(), method_lower.end(), method_lower.begin(), ::tolower); std::string req = make_package_prefix(package, request); fprintf(this->out_file, this->client_main_create_task_format.c_str(), req.c_str(), method_lower.c_str(), service.c_str(), method.c_str(), method_lower.c_str(), method.c_str(), method_lower.c_str(), method_lower.c_str(), method_lower.c_str(), method_lower.c_str()); } */ void print_client_main_end() { fprintf(this->out_file, "%s", this->client_main_end_format.c_str()); if (!this->is_thrift) fprintf(this->out_file, "%s", this->main_pb_shutdown_format.c_str()); fprintf(this->out_file, "%s", this->main_end_return_format.c_str()); } void print_end(const std::vector& package) { for (size_t i = package.size() - 1; i != (size_t)-1; i--) fprintf(this->out_file, namespace_package_end_format.c_str(), package[i].c_str()); // fprintf(this->out_file, "} // namespace srpc\n"); } void print_empty_line() { fprintf(this->out_file, "\n"); } Printer(bool is_thrift) { this->is_thrift = is_thrift; } protected: FILE *out_file; bool is_thrift; private: std::string thrift_include_format = R"(#pragma once #include "srpc/rpc_thrift_idl.h" )"; std::string srpc_include_format = R"(#pragma once #include #include #include "srpc/rpc_define.h" #include "%s.%s.h" )"; std::string thrift_include_package_format = R"( namespace %s { )"; std::string srpc_include_package_format = R"( using namespace %s; )"; std::string srpc_file_include_format = R"(#include "%s.srpc.h" #include "workflow/WFFacilities.h" )"; std::string using_namespace_sogou_format = R"( using namespace srpc; )"; std::string wait_group_declaration_format = R"( static WFFacilities::WaitGroup wait_group(1); void sig_handler(int signo) { wait_group.done(); } )"; /* std::string namespace_total_start_format = R"( namespace srpc { )"; */ std::string namespace_package_start_format = R"( namespace %s { )"; std::string namespace_package_end_format = R"(} // end namespace %s )"; std::string server_comment_format = R"( /* * Server codes * Generated by SRPC */ )"; std::string client_comment_format = R"( /* * Client codes * Generated by SRPC */ )"; std::string client_define_done_format = R"( using %sDone = std::function;)"; std::string client_define_task_format = R"(using %sTask = srpc::RPCClientTask<%s, %s>; )"; std::string server_context_format = R"( using %sContext = srpc::RPCContext<%s, %s>; )"; std::string server_controller_format = R"( class Server%sCntl : public srpc::%sCntl { public: // must implement this in server codes void %s(%s *request, %s *response); Server%sCntl(srpc::RPCTask *task) : %sCntl(task) {} }; )"; std::string client_controller_format = R"( class Client%sCntl; typedef std::function %sDone; class Client%sCntl : public srpc::%sCntl { public: Client%sCntl(srpc::RPCTask *task) : %sCntl(task) {} void %s(%sDone done, SeriesWork *series) { this->done = std::move(done); if (series != NULL) { this->set_series(series); series->push_back(this->get_task()); } this->service->call_method("%s", this); } %sDone done; }; )"; std::string server_method_format = R"( void %s(srpc::RPCTask *task, %s *request, %s *response); static inline void %s%s(srpc::RPCTask *task, \ rpc_buf_t *req_buf, rpc_buf_t *resp_buf) { %s pb_req; %s pb_resp; pb_req.ParseFromArray(req_buf->buf, req_buf->len); %s(task, &pb_req, &pb_resp); resp_buf->buf = malloc(pb_resp.ByteSizeLong()); resp_buf->len = pb_resp.ByteSizeLong(); pb_resp.SerializeToArray(resp_buf->buf, resp_buf->len); } )"; std::string server_class_constructor_format = R"( class Service : public srpc::RPCService { public: // please implement these methods in server.cc )"; std::string server_class_end_format = R"( public: Service(); }; )"; std::string server_class_method_format = R"( virtual void %s(%s *request, %s *response, srpc::RPCContext *ctx) = 0; )"; std::string server_class_method_declaration_thrift_format = R"( virtual void %s(%s *request, %s *response, srpc::RPCContext *ctx); )"; std::string server_class_method_implementation_thrift_format = R"( inline void Service::%s(%s *request, %s *response, srpc::RPCContext *ctx) { %sthis->%s(%s); } )"; std::string server_class_method_declaration_thrift_handler_format = R"( virtual %s %s(%s); )"; std::string server_class_method_implementation_thrift_handler_format = R"( inline %s Service::%s(%s) {%s} )"; std::string server_class_construct_method_format = R"( void %s%s(srpc::RPCTask *task); )"; std::string server_constructor_method_format = R"( inline Service::Service(): srpc::RPCService("%s") {)"; std::string server_constructor_add_method_format = R"( this->srpc::RPCService::add_method("%s", [this](srpc::RPCWorker& worker) ->int { return ServiceRPCCallImpl(this, worker, &Service::%s); }); )"; std::string server_methods_format = R"( void %s::%s%s(srpc::RPCTask *task) { Server%sCntl cntl(task); srpc::RPCService::parse_from_buffer(task, cntl.get_input(), true); cntl.%s(cntl.get_req(), cntl.get_resp()); srpc::RPCService::serialize_to_buffer(task, cntl.get_output(), false); } )"; std::string client_class_constructor_start_format = R"( class %sClient : public srpc::%sClient { public:)"; std::string client_constructor_methods_format = R"( inline %sClient::%sClient(const char *host, unsigned short port): srpc::%sClient("%s") { struct srpc::RPCClientParams params = srpc::RPC_CLIENT_PARAMS_DEFAULT; %s params.host = host; params.port = port; this->srpc::%sClient::init(¶ms); } inline %sClient::%sClient(const struct srpc::RPCClientParams *params): srpc::%sClient("%s") { const struct srpc::RPCClientParams *temp = params; struct srpc::RPCClientParams temp_params; %s this->srpc::%sClient::init(temp); } )"; std::string client_constructor_methods_ip_srpc_thrift_format = R"( params.task_params.data_type = srpc::RPCDataThrift; )"; std::string client_constructor_methods_params_srpc_thrift_format = R"( if (params->task_params.data_type == srpc::RPCDataUndefined) { temp_params = *temp; temp_params.task_params.data_type = srpc::RPCDataThrift; temp = &temp_params; } )"; std::string client_class_constructor_ip_port_format = R"( %sClient(const char *host, unsigned short port) : srpc::RPCService("%s") { this->params.service_params.host = host; this->params.service_params.port = port; this->register_compress_type(srpc::RPCCompressGzip); )"; std::string client_class_functions = R"( const struct srpc::RPCClientParams *get_params() { return &this->params; } )"; std::string client_class_variables = R"( struct srpc::RPCClientParams params; )"; std::string tab_right_braket = R"( } )"; std::string client_class_done_format = R"( %sDone %s_done; )"; std::string client_class_construct_methods_thrift_format = R"( %s %s(%s); void send_%s(%s); %s recv_%s(%s); )"; std::string client_class_get_last_thrift_format = R"( bool thrift_last_sync_success() const; const srpc::RPCSyncContext& thrift_last_sync_ctx() const; )"; std::string client_class_private_get_thrift_format = R"( private: static srpc::RPCSyncContext *get_thread_sync_ctx(); )"; std::string client_class_get_rpc_thread_resp_thrift_format = R"( static srpc::ThriftReceiver<%s> *get_thread_sync_receiver_%s(); )"; std::string client_class_construct_methods_format = R"( void %s(const %s *req, %sDone done); void %s(const %s *req, %s *resp, srpc::RPCSyncContext *sync_ctx); WFFuture> async_%s(const %s *req); )"; std::string client_class_constructor_format = R"( public: %sClient(const char *host, unsigned short port); %sClient(const struct srpc::RPCClientParams *params); public: )"; std::string client_class_create_methods_format = R"( srpc::%sClientTask *create_%s_task(%sDone done); )"; //%sClientTask *create_%s_task(%s *req, %sDone done); std::string client_class_constructor_end_format = R"(}; )"; std::string client_method_format = R"( inline void %sClient::%s(const %s *req, %sDone done) { auto *task = this->create_rpc_client_task("%s", std::move(done)); task->serialize_input(req); task->start(); } inline void %sClient::%s(const %s *req, %s *resp, srpc::RPCSyncContext *sync_ctx) { auto res = this->async_%s(req).get(); if (resp && res.second.success) *resp = std::move(res.first); if (sync_ctx) *sync_ctx = std::move(res.second); } inline WFFuture> %sClient::async_%s(const %s *req) { using RESULT = std::pair<%s, srpc::RPCSyncContext>; auto *pr = new WFPromise(); auto fr = pr->get_future(); auto *task = this->create_rpc_client_task<%s>("%s", srpc::RPCAsyncFutureCallback<%s>); task->serialize_input(req); task->user_data = pr; task->start(); return fr; } )"; std::string client_method_trpc_format = R"( inline void %sClient::%s(const %s *req, %sDone done) { auto *task = this->create_rpc_client_task("%s", std::move(done)); if (this->params.callee_timeout >= 0) task->get_req()->set_callee_timeout(this->params.callee_timeout); if (!this->params.caller.empty()) task->get_req()->set_caller_name(this->params.caller); task->serialize_input(req); task->start(); } inline void %sClient::%s(const %s *req, %s *resp, srpc::RPCSyncContext *sync_ctx) { auto res = this->async_%s(req).get(); if (resp && res.second.success) *resp = std::move(res.first); if (sync_ctx) *sync_ctx = std::move(res.second); } inline WFFuture> %sClient::async_%s(const %s *req) { using RESULT = std::pair<%s, srpc::RPCSyncContext>; auto *pr = new WFPromise(); auto fr = pr->get_future(); auto *task = this->create_rpc_client_task<%s>("%s", srpc::RPCAsyncFutureCallback<%s>); if (this->params.callee_timeout >= 0) task->get_req()->set_callee_timeout(this->params.callee_timeout); if (!this->params.caller.empty()) task->get_req()->set_caller_name(this->params.caller); task->serialize_input(req); task->user_data = pr; task->start(); return fr; } )"; std::string client_method_thrift_begin_format = R"( inline srpc::ThriftReceiver<%s> *%sClient::get_thread_sync_receiver_%s() { static thread_local srpc::ThriftReceiver<%s> receiver; return &receiver; } inline %s %sClient::%s(%s) { %s __thrift__sync__req; %s __thrift__sync__resp; )"; std::string client_method_thrift_end_format = R"( this->%s(&__thrift__sync__req, &__thrift__sync__resp, %sClient::get_thread_sync_ctx()); %s; } inline void %sClient::send_%s(%s) { %s __thrift__sync__req; )"; std::string cilent_method_thrift_send_end_format = R"( auto *task = this->create_rpc_client_task<%s>("%s", srpc::ThriftSendCallback<%s>); task->serialize_input(&__thrift__sync__req); task->user_data = get_thread_sync_receiver_%s(); task->start(); } inline %s %sClient::recv_%s(%s) { int cookie = 0; auto *receiver = get_thread_sync_receiver_%s(); std::unique_lock lock(receiver->mutex); if (!receiver->is_done) { cookie = WFGlobal::sync_operation_begin(); while (!receiver->is_done) receiver->cond.wait(lock); } receiver->is_done = false; *get_thread_sync_ctx() = std::move(receiver->ctx); auto res = std::move(receiver->output); lock.unlock(); WFGlobal::sync_operation_end(cookie); %s; } )"; std::string client_method_get_last_thrift_format = R"( inline srpc::RPCSyncContext *%sClient::get_thread_sync_ctx() { static thread_local srpc::RPCSyncContext thread_sync_ctx; return &thread_sync_ctx; } inline bool %sClient::thrift_last_sync_success() const { return get_thread_sync_ctx()->success; } inline const srpc::RPCSyncContext& %sClient::thrift_last_sync_ctx() const { return *get_thread_sync_ctx(); } )"; std::string client_create_task_format = R"( inline srpc::%sClientTask *%sClient::create_%s_task(%sDone done) { return this->create_rpc_client_task("%s", std::move(done)); } )"; std::string client_create_task_trpc_format = R"( inline srpc::%sClientTask *%sClient::create_%s_task(%sDone done) { auto *task = this->create_rpc_client_task("%s", std::move(done)); if (!this->params.caller.empty()) task->get_req()->set_caller_name(this->params.caller); return task; } )"; /* inline %sClientTask *%sClient::create_%s_task(%s *req, %sDone done) { auto *task = this->create_rpc_client_task("%s", std::move(done)); task->serialize_input(req); return task; }*/ std::string namespace_service_start_format = R"( namespace %s { )"; std::string namespace_service_end_format = R"( } // end namespace %s )"; std::string namespace_method_task_format = R"( static Client%sCntl *create_%s_cntl(%sClient *stub) { rpc_callback_t cb = std::bind(&%sClient::%sCallback, stub, std::placeholders::_1); auto *task = new ComplexRPCClientTask(stub->get_params(), std::move(cb)); if (task->get_error()) { errno = task->get_error(); delete task; return NULL; } Client%sCntl *cntl = new Client%sCntl(task); cntl->set_service(stub); task->set_controller(cntl); return cntl; } )"; std::string print_server_class_impl_format = R"( class %sServiceImpl : public %s::Service { public: )"; std::string server_impl_method_format = R"( void %s(%s *request, %s *response, srpc::RPCContext *ctx) override { // TODO: fill server logic here } )"; std::string server_main_begin_format = R"( int main() {)"; std::string server_main_ip_port_format = R"( unsigned short port = 1412; %sServer server; )"; std::string server_main_method_format = R"( %sServiceImpl %s_impl; server.add_service(&%s_impl); )"; std::string server_main_end_format = R"( server.start(port); wait_group.wait(); server.stop();)"; std::string client_done_format = R"( static void %s_done(%s *response, srpc::RPCContext *context) { } )"; std::string main_pb_version_format = R"( GOOGLE_PROTOBUF_VERIFY_VERSION;)"; std::string client_main_begin_format = R"( int main() {)"; std::string client_main_ip_port_format = R"( const char *ip = "127.0.0.1"; unsigned short port = 1412; )"; std::string client_main_service_format = R"( %s::%sClient client%s(ip, port); )"; std::string client_main_method_call_format = R"( // example for RPC method call %s %s_req; //%s_req.set_message("Hello, srpc!"); client%s.%s(&%s_req, %s_done); )"; /* std::string client_main_create_task_format = R"( // example for creating RPC task %s %s_req; srpc::RPC%s::%sTask *%s_task = client.create_%s_task(%s_done); %s_task->serialize_input(&%s_req); %s_task->start(); )"; */ std::string client_main_end_format = R"( wait_group.wait();)"; std::string main_pb_shutdown_format = R"( google::protobuf::ShutdownProtobufLibrary();)"; std::string main_end_return_format = R"( return 0; } )"; std::string thrift_struct_class_begin_format = R"( class %s : public srpc::ThriftIDLMessage { public: )"; std::string thrift_struct_class_constructor_end_format = R"( this->elements = srpc::ThriftElementsImpl<%s>::get_elements_instance(); this->descriptor = srpc::ThriftDescriptorImpl<%s, srpc::TDT_STRUCT, void, void>::get_instance(); } )"; std::string thrift_struct_class_end_format = R"( } friend class srpc::ThriftElementsImpl<%s>; }; )"; std::string thrift_struct_element_impl_begin_format = R"( private: static void StaticElementsImpl(std::list *elements) { const %s *st = (const %s *)0; const char *base = (const char *)st; (void)base; )"; std::string thrift_struct_class_set_required_format = R"( void __set_%s(const %s val) { this->%s = val; } )"; std::string thrift_struct_class_set_optional_format = R"( void __set_%s(const %s val) { this->%s = val; this->__isset.%s = true; } )"; std::string thrift_struct_class_isset_begin_format = R"( public: struct ISSET { )"; std::string thrift_struct_class_isset_end_format = R"( } __isset; )"; std::string thrift_struct_class_operator_equal_begin_format = R"( bool operator == (const %s& rhs) const { )"; std::string thrift_struct_class_operator_equal_required_format = R"( if (!(this->%s == rhs.%s)) return false; )"; std::string thrift_struct_class_operator_equal_optional_format = R"( if (this->__isset.%s != rhs.__isset.%s) return false; else if (this->__isset.%s && !(this->%s == rhs.%s)) return false; )"; std::string thrift_struct_class_operator_equal_end_format = R"( return true; } )"; std::string thrift_struct_class_elements_start_format = R"( bool operator != (const %s& rhs) const { return !(*this == rhs); } bool operator < (const %s & ) const; %s(const %s&) = default; %s(%s&&) = default; %s& operator= (const %s&) = default; %s& operator= (%s&&) = default; %s() { )"; }; #endif sogou-srpc-ac65014/src/http/000077500000000000000000000000001525217424400157145ustar00rootroot00000000000000sogou-srpc-ac65014/src/http/CMakeLists.txt000066400000000000000000000005471525217424400204620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(http) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SRC http_module.cc http_task.cc http_client.cc http_server.cc ) add_library(${PROJECT_NAME} OBJECT ${SRC}) if (WIN32) target_compile_definitions( ${PROJECT_NAME} PRIVATE strdup=_strdup strcasecmp=_stricmp strncasecmp=_strnicmp ) endif () sogou-srpc-ac65014/src/http/http_client.cc000066400000000000000000000071121525217424400205410ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include "http_client.h" #include "http_task.h" #include "http_module.h" namespace srpc { WFHttpTask *HttpClient::create_http_task(const std::string& url, int redirect_max, int retry_max, http_callback_t callback) { std::list module; for (int i = 0; i < SRPC_MODULE_MAX; i++) { if (this->modules[i]) module.push_back(this->modules[i]); } auto&& cb = std::bind(&HttpClient::callback, this, std::placeholders::_1); HttpClientTask *task = new HttpClientTask(redirect_max, retry_max, std::move(module), std::move(cb)); ParsedURI uri; URIParser::parse(url, uri); task->init(std::move(uri)); task->set_keep_alive(HTTP_KEEPALIVE_DEFAULT); // task->set_url(url); task->user_callback_ = std::move(callback); return task; } WFHttpTask *HttpClient::create_http_task(const ParsedURI& uri, int redirect_max, int retry_max, http_callback_t callback) { std::list module; for (int i = 0; i < SRPC_MODULE_MAX; i++) { if (this->modules[i]) module.push_back(this->modules[i]); } auto&& cb = std::bind(&HttpClient::callback, this, std::placeholders::_1); HttpClientTask *task = new HttpClientTask(redirect_max, retry_max, std::move(module), std::move(cb)); task->init(uri); task->set_keep_alive(HTTP_KEEPALIVE_DEFAULT); // task->set_url(url); task->user_callback_ = std::move(callback); return task; } void HttpClient::init() { URIParser::parse(this->params.url, this->params.uri); if (this->params.uri.scheme && strcasecmp(this->params.uri.scheme, "https") == 0) { this->params.is_ssl = true; } } void HttpClient::callback(WFHttpTask *task) { HttpClientTask *client_task = (HttpClientTask *)task; const std::list& module_list = client_task->get_module_list(); void *series_data; RPCModuleData *resp_data; if (!module_list.empty()) { resp_data = client_task->mutable_module_data(); if (resp_data->empty()) // get series module data failed previously { series_data = series_of(task)->get_specific(SRPC_MODULE_DATA); if (series_data) resp_data = (RPCModuleData *)series_data; } // else // http_get_header_module_data(task->get_resp(), *resp_data); for (RPCModule *module : module_list) { if (!module->client_task_end(task, *resp_data)) break; } } if (client_task->user_callback_) client_task->user_callback_(task); } void HttpClient::add_filter(RPCFilter *filter) { int type = filter->get_module_type(); this->mutex.lock(); if (type < SRPC_MODULE_MAX && type >= 0) { RPCModule *module = this->modules[type]; if (!module) { switch (type) { case RPCModuleTypeTrace: module = new HttpTraceModule(); break; case RPCModuleTypeMetrics: module = new HttpMetricsModule(); break; case RPCModuleTypeCustom: module = new HttpCustomModule(); break; default: break; } this->modules[type] = module; } if (module) module->add_filter(filter); } this->mutex.unlock(); return; } } // end namespace srpc sogou-srpc-ac65014/src/http/http_client.h000066400000000000000000000044141525217424400204050ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #ifndef __RPC_HTTP_CLIENT_H__ #define __RPC_HTTP_CLIENT_H__ #include "workflow/WFTask.h" #include "workflow/HttpMessage.h" #include "workflow/WFTaskFactory.h" #include "rpc_options.h" #include "rpc_basic.h" #include "http_task.h" namespace srpc { #define HTTP_REDIRECT_DEFAULT 2 #define HTTP_RETRY_DEFAULT 5 #define HTTP_KEEPALIVE_DEFAULT (60 * 1000) struct HttpTaskParams { int send_timeout; int receive_timeout; int watch_timeout; int keep_alive_timeout; int redirect_max; int retry_max; }; struct HttpClientParams { HttpTaskParams task_params; bool is_ssl; std::string url; // can be empty ParsedURI uri; }; static constexpr struct HttpTaskParams HTTP_TASK_PARAMS_DEFAULT = { /* .send_timeout = */ -1, /* .receive_timeout = */ -1, /* .watch_timeout = */ 0, /* .keep_alive_timeout = */ HTTP_KEEPALIVE_DEFAULT, /* .retry_max = */ HTTP_REDIRECT_DEFAULT, /* .redirect_max = */ HTTP_RETRY_DEFAULT, }; static const struct HttpClientParams HTTP_CLIENT_PARAMS_DEFAULT = { /* .task_params = */ HTTP_TASK_PARAMS_DEFAULT, /* .is_ssl = */ false, /* .url = */ "", /* .uri = */ }; class HttpClient { public: HttpClient() { } WFHttpTask *create_http_task(const std::string& url, int redirect_max, int retry_max, http_callback_t callback); WFHttpTask *create_http_task(const ParsedURI& uri, int redirect_max, int retry_max, http_callback_t callback); void add_filter(RPCFilter *filter); private: void callback(WFHttpTask *task); void init(); void task_init(HttpClientTask *task); protected: HttpClientParams params; ParsedURI uri; private: std::mutex mutex; RPCModule *modules[SRPC_MODULE_MAX] = { 0 }; }; } // end namespace srpc #endif sogou-srpc-ac65014/src/http/http_module.cc000066400000000000000000000113121525217424400205450ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include "http_module.h" #include "http_task.h" #ifdef _WIN32 #include #else #include #include #endif namespace srpc { bool HttpTraceModule::client_begin(SubTask *task, RPCModuleData& data) { TraceModule::client_begin(task, data); auto *client_task = static_cast(task); TraceModule::client_begin_request(client_task->get_req(), data); data[SRPC_COMPONENT] = SRPC_COMPONENT_SRPC; data[SRPC_HTTP_PEER_NAME] = client_task->get_uri_host(); data[SRPC_HTTP_PEER_PORT] = client_task->get_uri_port(); data[SRPC_HTTP_SCHEME] = client_task->get_uri_scheme(); // TODO: data[SRPC_HTTP_CLIENT_URL] = client_task->get_url(); return true; } bool HttpTraceModule::client_end(SubTask *task, RPCModuleData& data) { TraceModule::client_end(task, data); auto *client_task = static_cast(task); data[WF_TASK_STATE] = std::to_string(client_task->get_state()); if (client_task->get_state() != WFT_STATE_SUCCESS) { data[WF_TASK_ERROR] = std::to_string(client_task->get_error()); if (client_task->get_error() == ETIMEDOUT) { data[SRPC_TIMEOUT_REASON] = std::to_string(client_task->get_timeout_reason()); } return true; } TraceModule::client_end_response(client_task->get_resp(), data); data[SRPC_HTTP_RESEND_COUNT] = std::to_string(client_task->get_retry_times()); char addrstr[128]; struct sockaddr_storage addr; socklen_t l = sizeof addr; unsigned short port = 0; client_task->get_peer_addr((struct sockaddr *)&addr, &l); if (addr.ss_family == AF_INET) { data[SRPC_HTTP_SOCK_FAMILY] = "inet"; struct sockaddr_in *sin = (struct sockaddr_in *)&addr; inet_ntop(AF_INET, &sin->sin_addr, addrstr, 128); port = ntohs(sin->sin_port); } else if (addr.ss_family == AF_INET6) { data[SRPC_HTTP_SOCK_FAMILY] = "inet6"; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr; inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, 128); port = ntohs(sin6->sin6_port); } else memset(addrstr, 0, sizeof (addrstr)); data[SRPC_HTTP_SOCK_ADDR] = addrstr; data[SRPC_HTTP_SOCK_PORT] = std::to_string(port); return true; } bool HttpTraceModule::server_begin(SubTask *task, RPCModuleData& data) { TraceModule::server_begin(task, data); auto *server_task = static_cast(task); TraceModule::server_begin_request(server_task->get_req(), data); data[SRPC_COMPONENT] = SRPC_COMPONENT_SRPC; data[SRPC_HTTP_SCHEME] = server_task->is_ssl() ? "https" : "http"; data[SRPC_HTTP_HOST_PORT] = std::to_string(server_task->listen_port()); char addrstr[128]; struct sockaddr_storage addr; socklen_t l = sizeof addr; unsigned short port = 0; server_task->get_peer_addr((struct sockaddr *)&addr, &l); if (addr.ss_family == AF_INET) { data[SRPC_HTTP_SOCK_FAMILY] = "inet"; struct sockaddr_in *sin = (struct sockaddr_in *)&addr; inet_ntop(AF_INET, &sin->sin_addr, addrstr, 128); port = ntohs(sin->sin_port); } else if (addr.ss_family == AF_INET6) { data[SRPC_HTTP_SOCK_FAMILY] = "inet6"; struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr; inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, 128); port = ntohs(sin6->sin6_port); } // else : Unknown data[SRPC_HTTP_SOCK_ADDR] = addrstr; data[SRPC_HTTP_SOCK_PORT] = std::to_string(port); return true; } bool HttpTraceModule::server_end(SubTask *task, RPCModuleData& data) { TraceModule::server_end(task, data); auto *server_task = static_cast(task); data[SRPC_STATE] = std::to_string(server_task->get_state()); if (server_task->get_state() != WFT_STATE_SUCCESS) { data[SRPC_ERROR] = std::to_string(server_task->get_error()); if (server_task->get_error() == ETIMEDOUT) { data[SRPC_TIMEOUT_REASON] = std::to_string(server_task->get_timeout_reason()); } return true; } TraceModule::server_end_response(server_task->get_resp(), data); return true; } bool HttpMetricsModule::client_begin(SubTask *task, RPCModuleData& data) { MetricsModule::client_begin(task, data); // TODO: return true; } bool HttpMetricsModule::server_begin(SubTask *task, RPCModuleData& data) { MetricsModule::server_begin(task, data); // TODO: return true; } } // end namespace srpc sogou-srpc-ac65014/src/http/http_module.h000066400000000000000000000025051525217424400204130ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #ifndef __RPC_HTTP_MODULE_H__ #define __RPC_HTTP_MODULE_H__ #include "rpc_basic.h" #include "rpc_module.h" #include "rpc_trace_module.h" #include "rpc_metrics_module.h" namespace srpc { class HttpTraceModule : public TraceModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool client_end(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; }; class HttpMetricsModule : public MetricsModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; }; using HttpCustomModule = CustomModule; } // end namespace srpc #endif sogou-srpc-ac65014/src/http/http_server.cc000066400000000000000000000050111525217424400205650ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include "http_server.h" #include "http_task.h" #include "http_module.h" #ifdef _WIN32 #include #else #include #include #endif namespace srpc { void HttpServer::add_filter(RPCFilter *filter) { int type = filter->get_module_type(); this->mutex.lock(); if (type < SRPC_MODULE_MAX && type >= 0) { RPCModule *module = this->modules[type]; if (!module) { switch (type) { case RPCModuleTypeTrace: module = new HttpTraceModule(); break; case RPCModuleTypeMetrics: module = new HttpMetricsModule(); break; case RPCModuleTypeCustom: module = new HttpCustomModule(); break; default: break; } this->modules[type] = module; } if (module) module->add_filter(filter); } this->mutex.unlock(); return; } CommSession *HttpServer::new_session(long long seq, CommConnection *conn) { HttpServerTask *task; std::list module; for (int i = 0; i < SRPC_MODULE_MAX; i++) { if (this->modules[i]) module.push_back(this->modules[i]); } task = new HttpServerTask(this, std::move(module), this->process); task->set_keep_alive(this->params.keep_alive_timeout); task->set_receive_timeout(this->params.receive_timeout); task->get_req()->set_size_limit(this->params.request_size_limit); task->set_is_ssl(this->get_ssl_ctx() != NULL); task->set_listen_port(this->get_listen_port()); return task; } unsigned short HttpServer::get_listen_port() { if (this->listen_port == 0) { struct sockaddr_storage addr; socklen_t l = sizeof addr; this->get_listen_addr((struct sockaddr *)&addr, &l); if (addr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&addr; this->listen_port = ntohs(sin->sin_port); } else // if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr; this->listen_port = ntohs(sin6->sin6_port); } } return this->listen_port; } } // namespace srpc sogou-srpc-ac65014/src/http/http_server.h000066400000000000000000000023011525217424400204260ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #ifndef __RPC_HTTP_SERVER_H__ #define __RPC_HTTP_SERVER_H__ #include "workflow/WFHttpServer.h" #include "rpc_basic.h" #include "rpc_filter.h" #include "rpc_module.h" namespace srpc { class HttpServer : public WFHttpServer { public: HttpServer(http_process_t proc) : WFHttpServer(std::move(proc)), listen_port(0) { } void add_filter(RPCFilter *filter); protected: CommSession *new_session(long long seq, CommConnection *conn) override; private: unsigned short get_listen_port(); private: std::mutex mutex; RPCModule *modules[SRPC_MODULE_MAX] = { NULL }; unsigned short listen_port; }; } // namespace srpc #endif sogou-srpc-ac65014/src/http/http_task.cc000066400000000000000000000275201525217424400202320ustar00rootroot00000000000000#include #include #include #include #include "workflow/WFTaskError.h" #include "workflow/WFTaskFactory.h" #include "workflow/HttpUtil.h" #include "workflow/StringUtil.h" #include "rpc_module.h" #include "http_task.h" namespace srpc { using namespace protocol; #define HTTP_KEEPALIVE_MAX (300 * 1000) HttpClientTask::HttpClientTask(int redirect_max, int retry_max, std::list&& modules, http_callback_t&& callback) : WFComplexClientTask(retry_max, std::move(callback)), redirect_max_(redirect_max), redirect_count_(0), modules_(std::move(modules)) { protocol::HttpRequest *client_req = this->get_req(); client_req->set_method(HttpMethodGet); client_req->set_http_version("HTTP/1.1"); } std::string HttpClientTask::get_uri_host() const { if (uri_.state == URI_STATE_SUCCESS) return uri_.host; return ""; } std::string HttpClientTask::get_uri_port() const { if (uri_.state == URI_STATE_SUCCESS) return uri_.port; return ""; } std::string HttpClientTask::get_url() const { if (url_.empty()) return url_; return ""; //TODO: fill with uri and other info } std::string HttpClientTask::get_uri_scheme() const { if (uri_.state == URI_STATE_SUCCESS) return uri_.scheme; return ""; } bool HttpClientTask::check_request() { void *series_data = series_of(this)->get_specific(SRPC_MODULE_DATA); RPCModuleData *data = (RPCModuleData *)series_data; if (data) this->set_module_data(*data); data = this->mutable_module_data(); for (auto *module : modules_) { if (!module->client_task_begin(this, *data)) { this->state = WFT_STATE_TASK_ERROR; this->error = RPCStatusModuleFilterFailed; return false; } } http_set_header_module_data(*data, this->get_req()); return true; } CommMessageOut *HttpClientTask::message_out() { HttpRequest *req = this->get_req(); struct HttpMessageHeader header; bool is_alive; if (!req->is_chunked() && !req->has_content_length_header()) { size_t body_size = req->get_output_body_size(); const char *method = req->get_method(); if (body_size != 0 || strcmp(method, "POST") == 0 || strcmp(method, "PUT") == 0) { char buf[32]; header.name = "Content-Length"; header.name_len = strlen("Content-Length"); header.value = buf; header.value_len = sprintf(buf, "%zu", body_size); req->add_header(&header); } } if (req->has_connection_header()) is_alive = req->is_keep_alive(); else { header.name = "Connection"; header.name_len = strlen("Connection"); is_alive = (this->keep_alive_timeo != 0); if (is_alive) { header.value = "Keep-Alive"; header.value_len = strlen("Keep-Alive"); } else { header.value = "close"; header.value_len = strlen("close"); } req->add_header(&header); } if (!is_alive) this->keep_alive_timeo = 0; else if (req->has_keep_alive_header()) { HttpHeaderCursor req_cursor(req); //req---Connection: Keep-Alive //req---Keep-Alive: timeout=0,max=100 header.name = "Keep-Alive"; header.name_len = strlen("Keep-Alive"); if (req_cursor.find(&header)) { std::string keep_alive((const char *)header.value, header.value_len); std::vector params = StringUtil::split(keep_alive, ','); for (const auto& kv : params) { std::vector arr = StringUtil::split(kv, '='); if (arr.size() < 2) arr.emplace_back("0"); std::string key = StringUtil::strip(arr[0]); std::string val = StringUtil::strip(arr[1]); if (strcasecmp(key.c_str(), "timeout") == 0) { this->keep_alive_timeo = 1000 * atoi(val.c_str()); break; } } } if ((unsigned int)this->keep_alive_timeo > HTTP_KEEPALIVE_MAX) this->keep_alive_timeo = HTTP_KEEPALIVE_MAX; } return this->WFComplexClientTask::message_out(); } CommMessageIn *HttpClientTask::message_in() { HttpResponse *resp = this->get_resp(); if (strcmp(this->get_req()->get_method(), HttpMethodHead) == 0) resp->parse_zero_body(); return this->WFComplexClientTask::message_in(); } int HttpClientTask::keep_alive_timeout() { return this->resp.is_keep_alive() ? this->keep_alive_timeo : 0; } void HttpClientTask::set_empty_request() { HttpRequest *client_req = this->get_req(); client_req->set_request_uri("/"); client_req->set_header_pair("Host", ""); } void HttpClientTask::init_failed() { this->set_empty_request(); } bool HttpClientTask::init_success() { HttpRequest *client_req = this->get_req(); std::string request_uri; std::string header_host; bool is_ssl; if (uri_.scheme && strcasecmp(uri_.scheme, "http") == 0) is_ssl = false; else if (uri_.scheme && strcasecmp(uri_.scheme, "https") == 0) is_ssl = true; else { this->state = WFT_STATE_TASK_ERROR; this->error = WFT_ERR_URI_SCHEME_INVALID; this->set_empty_request(); return false; } //todo http+unix //https://stackoverflow.com/questions/26964595/whats-the-correct-way-to-use-a-unix-domain-socket-in-requests-framework //https://stackoverflow.com/questions/27037990/connecting-to-postgres-via-database-url-and-unix-socket-in-rails if (uri_.path && uri_.path[0]) request_uri = uri_.path; else request_uri = "/"; if (uri_.query && uri_.query[0]) { request_uri += "?"; request_uri += uri_.query; } if (uri_.host && uri_.host[0]) header_host = uri_.host; if (uri_.port && uri_.port[0]) { int port = atoi(uri_.port); if (is_ssl) { if (port != 443) { header_host += ":"; header_host += uri_.port; } } else { if (port != 80) { header_host += ":"; header_host += uri_.port; } } } this->WFComplexClientTask::set_transport_type(is_ssl ? TT_TCP_SSL : TT_TCP); client_req->set_request_uri(request_uri.c_str()); client_req->set_header_pair("Host", header_host.c_str()); return true; } bool HttpClientTask::redirect_url(HttpResponse *client_resp, ParsedURI& uri) { if (redirect_count_ < redirect_max_) { redirect_count_++; std::string url; HttpHeaderCursor cursor(client_resp); if (!cursor.find("Location", url) || url.empty()) { this->state = WFT_STATE_TASK_ERROR; this->error = WFT_ERR_HTTP_BAD_REDIRECT_HEADER; return false; } if (url[0] == '/') { if (url[1] != '/') { if (uri.port) url = ':' + (uri.port + url); url = "//" + (uri.host + url); } url = uri.scheme + (':' + url); } URIParser::parse(url, uri); return true; } return false; } bool HttpClientTask::need_redirect(ParsedURI& uri) { HttpRequest *client_req = this->get_req(); HttpResponse *client_resp = this->get_resp(); const char *status_code_str = client_resp->get_status_code(); const char *method = client_req->get_method(); if (!status_code_str || !method) return false; int status_code = atoi(status_code_str); switch (status_code) { case 301: case 302: case 303: if (redirect_url(client_resp, uri)) { if (strcasecmp(method, HttpMethodGet) != 0 && strcasecmp(method, HttpMethodHead) != 0) { client_req->set_method(HttpMethodGet); } return true; } else break; case 307: case 308: if (redirect_url(client_resp, uri)) return true; else break; default: break; } return false; } void HttpClientTask::check_response() { HttpResponse *resp = this->get_resp(); resp->end_parsing(); if (this->state == WFT_STATE_SYS_ERROR && this->error == ECONNRESET) { /* Servers can end the message by closing the connection. */ if (resp->is_header_complete() && !resp->is_keep_alive() && !resp->is_chunked() && !resp->has_content_length_header()) { this->state = WFT_STATE_SUCCESS; this->error = 0; } } } bool HttpClientTask::finish_once() { if (this->state != WFT_STATE_SUCCESS) this->check_response(); if (this->state == WFT_STATE_SUCCESS) { if (this->need_redirect(uri_)) this->set_redirect(uri_); else if (this->state != WFT_STATE_SUCCESS) this->disable_retry(); } return true; } /**********Server**********/ void HttpServerTask::handle(int state, int error) { if (state == WFT_STATE_TOREPLY) { HttpRequest *req = this->get_req(); // from WFHttpServerTask::handle() req_is_alive_ = req->is_keep_alive(); if (req_is_alive_ && req->has_keep_alive_header()) { HttpHeaderCursor req_cursor(req); struct HttpMessageHeader header; header.name = "Keep-Alive"; header.name_len = strlen("Keep-Alive"); req_has_keep_alive_header_ = req_cursor.find(&header); if (req_has_keep_alive_header_) { req_keep_alive_.assign((const char *)header.value, header.value_len); } } this->state = WFT_STATE_TOREPLY; this->target = this->get_target(); // fill module data from request to series http_get_header_module_data(req, this->module_data_); for (auto *module : this->modules_) { if (module) { if (!module->server_task_begin(this, this->module_data_)) { delete this; return; } } } ModuleSeries *series = new ModuleSeries(this); series->set_module_data(this->mutable_module_data()); series->start(); } else if (this->state == WFT_STATE_TOREPLY) { this->state = state; this->error = error; if (error == ETIMEDOUT) this->timeout_reason = TOR_TRANSMIT_TIMEOUT; // prepare module_data from series to response for (auto *module : modules_) { if (!module->server_task_end(this, this->module_data_)) { this->noreply(); this->error = RPCStatusModuleFilterFailed; break; } } if (this->error != RPCStatusModuleFilterFailed) http_set_header_module_data(this->module_data_, this->get_resp()); this->subtask_done(); } else delete this; } CommMessageOut *HttpServerTask::message_out() { HttpResponse *resp = this->get_resp(); struct HttpMessageHeader header; if (!resp->get_http_version()) resp->set_http_version("HTTP/1.1"); const char *status_code_str = resp->get_status_code(); if (!status_code_str || !resp->get_reason_phrase()) { int status_code; if (status_code_str) status_code = atoi(status_code_str); else status_code = HttpStatusOK; HttpUtil::set_response_status(resp, status_code); } if (!resp->is_chunked() && !resp->has_content_length_header()) { char buf[32]; header.name = "Content-Length"; header.name_len = strlen("Content-Length"); header.value = buf; header.value_len = sprintf(buf, "%zu", resp->get_output_body_size()); resp->add_header(&header); } bool is_alive; if (resp->has_connection_header()) is_alive = resp->is_keep_alive(); else is_alive = req_is_alive_; if (!is_alive) this->keep_alive_timeo = 0; else { //req---Connection: Keep-Alive //req---Keep-Alive: timeout=5,max=100 if (req_has_keep_alive_header_) { int flag = 0; std::vector params = StringUtil::split(req_keep_alive_, ','); for (const auto& kv : params) { std::vector arr = StringUtil::split(kv, '='); if (arr.size() < 2) arr.emplace_back("0"); std::string key = StringUtil::strip(arr[0]); std::string val = StringUtil::strip(arr[1]); if (!(flag & 1) && strcasecmp(key.c_str(), "timeout") == 0) { flag |= 1; // keep_alive_timeo = 5000ms when Keep-Alive: timeout=5 this->keep_alive_timeo = 1000 * atoi(val.c_str()); if (flag == 3) break; } else if (!(flag & 2) && strcasecmp(key.c_str(), "max") == 0) { flag |= 2; if (this->get_seq() >= atoi(val.c_str())) { this->keep_alive_timeo = 0; break; } if (flag == 3) break; } } } if ((unsigned int)this->keep_alive_timeo > HTTP_KEEPALIVE_MAX) this->keep_alive_timeo = HTTP_KEEPALIVE_MAX; //if (this->keep_alive_timeo < 0 || this->keep_alive_timeo > HTTP_KEEPALIVE_MAX) } if (!resp->has_connection_header()) { header.name = "Connection"; header.name_len = 10; if (this->keep_alive_timeo == 0) { header.value = "close"; header.value_len = 5; } else { header.value = "Keep-Alive"; header.value_len = 10; } resp->add_header(&header); } return this->WFServerTask::message_out(); } } // end namespace srpc sogou-srpc-ac65014/src/http/http_task.h000066400000000000000000000101571525217424400200720ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #ifndef __RPC_HTTP_TASK_H__ #define __RPC_HTTP_TASK_H__ #include #include #include #include #include "workflow/HttpUtil.h" #include "workflow/WFTaskFactory.h" #include "workflow/WFGlobal.h" #include "rpc_module.h" namespace srpc { // copy part of workflow/src/factory/HttpTaskImpl.cc class HttpClientTask : public WFComplexClientTask { public: HttpClientTask(int redirect_max, int retry_max, std::list&& modules, http_callback_t&& callback); RPCModuleData *mutable_module_data() { return &module_data_; } void set_module_data(RPCModuleData data) { module_data_ = std::move(data); } int get_retry_times() const { return retry_times_; } void set_url(std::string url) { this->url_ = std::move(url); } const std::list& get_module_list() const { return this->modules_; } std::string get_uri_host() const; std::string get_uri_port() const; std::string get_uri_scheme() const; std::string get_url() const; /* // similar to opentracing: log({{"event", "error"}, {"message", "application log"}}); void log(const RPCLogVector& fields); // Baggage Items, which are just key:value pairs that cross process boundaries void add_baggage(const std::string& key, const std::string& value); bool get_baggage(const std::string& key, std::string& value); */ protected: virtual bool check_request(); virtual CommMessageOut *message_out(); virtual CommMessageIn *message_in(); virtual int keep_alive_timeout(); virtual bool init_success(); virtual void init_failed(); virtual bool finish_once(); protected: bool need_redirect(ParsedURI& uri); bool redirect_url(protocol::HttpResponse *client_resp, ParsedURI& uri); void set_empty_request(); void check_response(); public: http_callback_t user_callback_; private: std::string url_; int redirect_max_; int redirect_count_; RPCModuleData module_data_; std::list modules_; }; class HttpServerTask : public WFServerTask { public: HttpServerTask(CommService *service, std::list&& modules, std::function& process) : WFServerTask(service, WFGlobal::get_scheduler(), process), req_is_alive_(false), req_has_keep_alive_header_(false), modules_(std::move(modules)) {} void set_is_ssl(bool is_ssl) { this->is_ssl_ = is_ssl; } void set_listen_port(unsigned short port) { this->listen_port_ = port; } bool is_ssl() const { return this->is_ssl_; } unsigned short listen_port() const { return this->listen_port_; } class ModuleSeries : public WFServerTask::Series { public: ModuleSeries(WFServerTask *task) : WFServerTask::Series(task), module_data_(NULL) {} RPCModuleData *get_module_data() { return module_data_; } void set_module_data(RPCModuleData *data) { module_data_ = data; } virtual void *get_specific(const char *key) { if (strcmp(key, SRPC_MODULE_DATA) == 0) return this->module_data_; else return NULL; } private: RPCModuleData *module_data_; }; RPCModuleData *mutable_module_data() { return &module_data_; } void set_module_data(RPCModuleData data) { module_data_ = std::move(data); } protected: virtual void handle(int state, int error); virtual CommMessageOut *message_out(); protected: bool req_is_alive_; bool req_has_keep_alive_header_; std::string req_keep_alive_; RPCModuleData module_data_; std::list modules_; bool is_ssl_; unsigned short listen_port_; }; } // end namespace srpc #endif sogou-srpc-ac65014/src/include/000077500000000000000000000000001525217424400163605ustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/000077500000000000000000000000001525217424400173275ustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/ckms_quantiles.h000077700000000000000000000000001525217424400271262../../var/ckms_quantiles.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/descriptor.h000077700000000000000000000000001525217424400266142../../generator/descriptor.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/generator.h000077700000000000000000000000001525217424400262342../../generator/generator.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/parser.h000077700000000000000000000000001525217424400250502../../generator/parser.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/printer.h000077700000000000000000000000001525217424400254262../../generator/printer.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_basic.h000077700000000000000000000000001525217424400241442../../rpc_basic.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_buffer.h000077700000000000000000000000001525217424400245242../../rpc_buffer.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_client.h000077700000000000000000000000001525217424400245362../../rpc_client.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_compress.h000077700000000000000000000000001525217424400273232../../compress/rpc_compress.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_compress_gzip.h000077700000000000000000000000001525217424400314052../../compress/rpc_compress_gzip.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_compress_lz4.h000077700000000000000000000000001525217424400307052../../compress/rpc_compress_lz4.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_compress_snappy.h000077700000000000000000000000001525217424400323072../../compress/rpc_compress_snappy.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_context.h000077700000000000000000000000001525217424400251522../../rpc_context.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_context.inl000077700000000000000000000000001525217424400260402../../rpc_context.inlustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_define.h000077700000000000000000000000001525217424400244662../../rpc_define.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_filter.h000077700000000000000000000000001525217424400260412../../module/rpc_filter.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_global.h000077700000000000000000000000001525217424400245022../../rpc_global.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_message.h000077700000000000000000000000001525217424400264762../../message/rpc_message.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_message_brpc.h000077700000000000000000000000001525217424400305122../../message/rpc_message_brpc.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_message_srpc.h000077700000000000000000000000001525217424400305542../../message/rpc_message_srpc.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_message_thrift.h000077700000000000000000000000001525217424400314362../../message/rpc_message_thrift.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_message_trpc.h000077700000000000000000000000001525217424400305562../../message/rpc_message_trpc.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_metrics_filter.h000077700000000000000000000000001525217424400313152../../module/rpc_metrics_filter.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_metrics_module.h000077700000000000000000000000001525217424400313152../../module/rpc_metrics_module.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_module.h000077700000000000000000000000001525217424400260412../../module/rpc_module.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_options.h000077700000000000000000000000001525217424400251702../../rpc_options.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_server.h000077700000000000000000000000001525217424400246162../../rpc_server.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_service.h000077700000000000000000000000001525217424400251022../../rpc_service.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_task.inl000077700000000000000000000000001525217424400245742../../rpc_task.inlustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_thrift_buffer.h000077700000000000000000000000001525217424400307642../../thrift/rpc_thrift_buffer.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_thrift_enum.h000077700000000000000000000000001525217424400301522../../thrift/rpc_thrift_enum.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_thrift_idl.h000077700000000000000000000000001525217424400275622../../thrift/rpc_thrift_idl.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_thrift_idl.inl000077700000000000000000000000001525217424400304502../../thrift/rpc_thrift_idl.inlustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_trace_filter.h000077700000000000000000000000001525217424400303752../../module/rpc_trace_filter.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_trace_module.h000077700000000000000000000000001525217424400303752../../module/rpc_trace_module.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_types.h000077700000000000000000000000001525217424400243122../../rpc_types.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_var.h000077700000000000000000000000001525217424400241522../../var/rpc_var.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/rpc_zero_copy_stream.h000077700000000000000000000000001525217424400307522../../rpc_zero_copy_stream.hustar00rootroot00000000000000sogou-srpc-ac65014/src/include/srpc/time_window_quantiles.h000077700000000000000000000000001525217424400321062../../var/time_window_quantiles.hustar00rootroot00000000000000sogou-srpc-ac65014/src/message/000077500000000000000000000000001525217424400163615ustar00rootroot00000000000000sogou-srpc-ac65014/src/message/CMakeLists.txt000066400000000000000000000010421525217424400211160ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(message) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(PROTO_LIST rpc_meta.proto rpc_meta_brpc.proto rpc_meta_trpc.proto) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_LIST}) set(SRC rpc_message_brpc.cc rpc_message_srpc.cc rpc_message_thrift.cc rpc_message_trpc.cc ${PROTO_SRCS} ${PROTO_HDRS} ) add_library(${PROJECT_NAME} OBJECT ${SRC}) if (WIN32) target_compile_definitions( ${PROJECT_NAME} PRIVATE strdup=_strdup strcasecmp=_stricmp strncasecmp=_strnicmp ) endif () sogou-srpc-ac65014/src/message/rpc_message.h000066400000000000000000000112071525217424400210230ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_MESSAGE_H__ #define __RPC_MESSAGE_H__ #include #include #include #include "rpc_basic.h" #include "rpc_filter.h" #include "rpc_thrift_idl.h" namespace srpc { class RPCRequest { public: virtual bool serialize_meta() = 0; virtual bool deserialize_meta() = 0; virtual const std::string& get_service_name() const = 0; virtual const std::string& get_method_name() const = 0; virtual void set_service_name(const std::string& service_name) = 0; virtual void set_method_name(const std::string& method_name) = 0; virtual bool get_meta_module_data(RPCModuleData& data) const = 0; virtual bool set_meta_module_data(const RPCModuleData& data) = 0; virtual void set_seqid(long long seqid) {} public: virtual ~RPCRequest() { } }; class RPCResponse { public: virtual bool serialize_meta() = 0; virtual bool deserialize_meta() = 0; virtual int get_status_code() const = 0; virtual int get_error() const = 0; virtual const char *get_errmsg() const = 0; virtual void set_status_code(int code) = 0; virtual void set_error(int error) = 0; virtual bool set_http_code(int code) { return false; } public: virtual ~RPCResponse() { } }; class RPCMessage { public: virtual void set_data_type(int type) = 0; virtual void set_compress_type(int type) = 0; virtual int get_compress_type() const = 0; virtual int get_data_type() const = 0; public: //return RPCStatus virtual int compress() = 0; virtual int decompress() = 0; virtual bool get_meta_module_data(RPCModuleData& data) const = 0; virtual bool set_meta_module_data(const RPCModuleData& data) = 0; virtual bool set_http_header(const std::string& name, const std::string& value) { return false; } virtual bool add_http_header(const std::string& name, const std::string& value) { return false; } virtual bool get_http_header(const std::string& name, std::string& value) const { return false; } virtual void set_json_add_whitespace(bool on); virtual bool get_json_add_whitespace() const; virtual void set_json_enums_as_ints(bool on); virtual bool get_json_enums_as_ints() const; virtual void set_json_preserve_names(bool on); virtual bool get_json_preserve_names() const; virtual void set_json_fields_no_presence(bool on); virtual bool get_json_fields_no_presence() const; public: //pb virtual int serialize(const ProtobufIDLMessage *idl_msg) { return RPCStatusIDLSerializeNotSupported; } virtual int deserialize(ProtobufIDLMessage *idl_msg) { return RPCStatusIDLDeserializeNotSupported; } public: //thrift virtual int serialize(const ThriftIDLMessage *idl_msg) { return RPCStatusIDLSerializeNotSupported; } virtual int deserialize(ThriftIDLMessage *idl_msg) { return RPCStatusIDLDeserializeNotSupported; } public: RPCMessage() { this->flags = 0; } virtual ~RPCMessage() { } protected: uint32_t flags; }; // implementation inline void RPCMessage::set_json_add_whitespace(bool on) { if (on) this->flags |= SRPC_JSON_OPTION_ADD_WHITESPACE; else this->flags &= ~SRPC_JSON_OPTION_ADD_WHITESPACE; } inline bool RPCMessage::get_json_add_whitespace() const { return this->flags & SRPC_JSON_OPTION_ADD_WHITESPACE; } inline void RPCMessage::set_json_enums_as_ints(bool on) { if (on) this->flags |= SRPC_JSON_OPTION_ENUM_AS_INITS; else this->flags &= ~SRPC_JSON_OPTION_ENUM_AS_INITS; } inline bool RPCMessage::get_json_enums_as_ints() const { return this->flags & SRPC_JSON_OPTION_ENUM_AS_INITS; } inline void RPCMessage::set_json_preserve_names(bool on) { if (on) this->flags |= SRPC_JSON_OPTION_PRESERVE_NAMES; else this->flags &= ~SRPC_JSON_OPTION_PRESERVE_NAMES; } inline bool RPCMessage::get_json_preserve_names() const { return this->flags & SRPC_JSON_OPTION_PRESERVE_NAMES; } inline void RPCMessage::set_json_fields_no_presence(bool on) { if (on) this->flags |= SRPC_JSON_OPTION_FIELDS_NO_PRECENCE; else this->flags &= ~SRPC_JSON_OPTION_FIELDS_NO_PRECENCE; } inline bool RPCMessage::get_json_fields_no_presence() const { return this->flags & SRPC_JSON_OPTION_FIELDS_NO_PRECENCE; } } // namespace srpc #endif sogou-srpc-ac65014/src/message/rpc_message_brpc.cc000066400000000000000000000367671525217424400222110ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #include #include #include "rpc_basic.h" #include "rpc_compress.h" #include "rpc_meta_brpc.pb.h" #include "rpc_message_brpc.h" #include "rpc_zero_copy_stream.h" namespace srpc { static constexpr int BRPC_ENOSERVICE = 1001; static constexpr int BRPC_ENOMETHOD = 1002; static constexpr int BRPC_EREQUEST = 1003; static constexpr int BRPC_EINTERNAL = 2001; static constexpr int BRPC_ERESPONSE = 2002; static constexpr int BRPC_ELOGOFF = 2003; BRPCMessage::BRPCMessage() { this->nreceived = 0; this->meta_buf = NULL; this->meta_len = 0; this->message_len = 0; this->attachment_len = 0; memset(this->header, 0, sizeof (this->header)); this->meta = new BrpcMeta(); this->message = new RPCBuffer(); this->attachment = NULL; } bool BRPCRequest::deserialize_meta() { BrpcMeta *meta = static_cast(this->meta); if (meta->ParseFromArray(this->meta_buf, (int)this->meta_len)) { if (meta->has_attachment_size()) { this->attachment_len = meta->attachment_size(); this->message_len -= this->attachment_len; this->attachment = new RPCBuffer(); this->message->cut(this->message_len, this->attachment); } return true; } return false; } bool BRPCResponse::deserialize_meta() { BrpcMeta *meta = static_cast(this->meta); int error; if (meta->ParseFromArray(this->meta_buf, (int)this->meta_len)) { if (meta->has_attachment_size()) { this->attachment_len = meta->attachment_size(); this->message_len -= this->attachment_len; this->attachment = new RPCBuffer(); this->message->cut(this->message_len, this->attachment); } this->srpc_status_code = RPCStatusOK; if (meta->has_response()) { error = meta->mutable_response()->error_code(); if (error != 0) { this->srpc_status_code = this->error_code_brpc_srpc(error); this->srpc_error_msg = meta->mutable_response()->error_text(); } } return true; } return false; } int BRPCMessage::append(const void *buf, size_t *size, size_t size_limit) { uint32_t *p; size_t header_left, body_received, buf_len; if (this->nreceived < BRPC_HEADER_SIZE) { //receive header header_left = BRPC_HEADER_SIZE - this->nreceived; if (*size >= header_left) { //receive the whole header and ready to recieve body memcpy(this->header + this->nreceived, buf, header_left); this->nreceived += header_left; p = (uint32_t *)this->header + 1; buf_len = ntohl(*p); // payload_len p = (uint32_t *)this->header + 2; this->meta_len = ntohl(*p); if (this->meta_len > buf_len) { errno = EBADMSG; return -1; } this->message_len = buf_len - this->meta_len; // msg_len + attachment_len if (buf_len >= size_limit) { errno = EMSGSIZE; return -1; } else if (buf_len > 0) { if (*size - header_left > buf_len) *size = header_left + buf_len; this->meta_buf = new char[this->meta_len]; // this->buf = new char[this->message_len]; if (*size - header_left <= this->meta_len) { memcpy(this->meta_buf, (const char *)buf + header_left, *size - header_left); } else { memcpy(this->meta_buf, (const char *)buf + header_left, this->meta_len); // memcpy(this->buf, // (const char *)buf + header_left + this->meta_len, // *size - header_left - this->meta_len); this->message->append((const char *)buf + header_left + this->meta_len, *size - header_left - this->meta_len, BUFFER_MODE_COPY); } this->nreceived += *size - header_left; if (this->nreceived == BRPC_HEADER_SIZE + buf_len) return 1; else return 0; } else if (*size == header_left) { return 1; // means body_size == 0 and finish recieved header } else { // means buf_len < 0 errno = EBADMSG; return -1; } } else { // only receive header memcpy(this->header + this->nreceived, buf, *size); this->nreceived += *size; return 0; } } else { // have already received the header and now is for body only body_received = this->nreceived - BRPC_HEADER_SIZE; buf_len = this->meta_len + this->message_len; if (body_received + *size > buf_len) *size = buf_len - body_received; if (body_received + *size <= this->meta_len) { memcpy(this->meta_buf + body_received, buf, *size); } else if (body_received < this->meta_len) { memcpy(this->meta_buf + body_received, buf, this->meta_len - body_received); if (body_received + *size > this->meta_len)// useless. always true // memcpy(this->buf, (const char *)buf + this->meta_len - body_received, // *size - this->meta_len + body_received); this->message->append((const char *)buf + this->meta_len - body_received, *size - this->meta_len + body_received, BUFFER_MODE_COPY); } else { // memcpy(this->buf + body_received - this->meta_len, buf, *size); this->message->append((const char *)buf, *size, BUFFER_MODE_COPY); } this->nreceived += *size; return this->nreceived == BRPC_HEADER_SIZE + buf_len; } } bool BRPCRequest::serialize_meta() { this->meta_len = meta->ByteSizeLong(); this->meta_buf = new char[this->meta_len]; return this->meta->SerializeToArray(this->meta_buf, (int)this->meta_len); } bool BRPCResponse::serialize_meta() { BrpcMeta *meta = static_cast(this->meta); int error; if (this->srpc_status_code != RPCStatusOK) { error = this->error_code_srpc_brpc(this->srpc_status_code); meta->mutable_response()->set_error_code(error); meta->mutable_response()->set_error_text(this->srpc_error_msg); } this->meta_len = meta->ByteSizeLong(); this->meta_buf = new char[this->meta_len]; return meta->SerializeToArray(this->meta_buf, (int)this->meta_len); } int BRPCMessage::get_compress_type() const { BrpcMeta *meta = static_cast(this->meta); return meta->compress_type(); } void BRPCMessage::set_compress_type(int type) { BrpcMeta *meta = static_cast(this->meta); meta->set_compress_type(type); } void BRPCMessage::set_attachment_nocopy(const char *attachment, size_t len) { BrpcMeta *meta = static_cast(this->meta); this->attachment_len += len; meta->set_attachment_size(this->attachment_len); this->attachment = new RPCBuffer(); this->attachment->append(attachment, len, BUFFER_MODE_NOCOPY); } bool BRPCMessage::get_attachment_nocopy(const char **attachment, size_t *len) const { size_t tmp_len = (size_t)-1; const void *tmp_buf; if (this->attachment == NULL || this->attachment->fetch(&tmp_buf, &tmp_len) == false) { return false; } *attachment = (const char *)tmp_buf; *len = tmp_len; return true; } const std::string& BRPCRequest::get_service_name() const { BrpcMeta *meta = static_cast(this->meta); return meta->mutable_request()->service_name(); } const std::string& BRPCRequest::get_method_name() const { BrpcMeta *meta = static_cast(this->meta); return meta->mutable_request()->method_name(); } void BRPCRequest::set_service_name(const std::string& service_name) { BrpcMeta *meta = static_cast(this->meta); meta->mutable_request()->set_service_name(service_name); } void BRPCRequest::set_method_name(const std::string& method_name) { BrpcMeta *meta = static_cast(this->meta); meta->mutable_request()->set_method_name(method_name); } int64_t BRPCRequest::get_correlation_id() const { const BrpcMeta *meta = static_cast(this->meta); if (meta->has_correlation_id()) return meta->correlation_id(); return -1; } int BRPCResponse::get_status_code() const { return this->srpc_status_code; } void BRPCResponse::set_status_code(int code) { this->srpc_status_code = code; if (code != RPCStatusOK) this->srpc_error_msg = this->get_errmsg(); } int BRPCResponse::get_error() const { BrpcMeta *meta = static_cast(this->meta); return meta->mutable_response()->error_code(); } const char *BRPCResponse::get_errmsg() const { switch (this->srpc_status_code) { case RPCStatusOK: return "OK"; case RPCStatusUndefined: return "Undefined Error"; case RPCStatusServiceNotFound: return "Service Not Found"; case RPCStatusMethodNotFound: return "Method Not Found"; case RPCStatusMetaError: return "Meta Error"; case RPCStatusReqCompressSizeInvalid: return "Request Compress-size Invalid"; case RPCStatusReqDecompressSizeInvalid: return "Request Decompress-size Invalid"; case RPCStatusReqCompressNotSupported: return "Request Compress Not Supported"; case RPCStatusReqDecompressNotSupported: return "Request Decompress Not Supported"; case RPCStatusReqCompressError: return "Request Compress Error"; case RPCStatusReqDecompressError: return "Request Decompress Error"; case RPCStatusReqSerializeError: return "Request Serialize Error"; case RPCStatusReqDeserializeError: return "Request Deserialize Error"; case RPCStatusRespCompressSizeInvalid: return "Response Compress-size Invalid"; case RPCStatusRespDecompressSizeInvalid: return "Response Decompress-size Invalid"; case RPCStatusRespCompressNotSupported: return "Response Compress Not Supported"; case RPCStatusRespDecompressNotSupported: return "Response Decompress Not Supported"; case RPCStatusRespCompressError: return "Response Compress Error"; case RPCStatusRespDecompressError: return "Response Decompress Error"; case RPCStatusRespSerializeError: return "Response Serialize Error"; case RPCStatusRespDeserializeError: return "Response Deserialize Error"; case RPCStatusIDLSerializeNotSupported: return "IDL Serialize Not Supported"; case RPCStatusIDLDeserializeNotSupported: return "IDL Deserialize Not Supported"; case RPCStatusModuleFilterFailed: return "Module or filter check failed"; case RPCStatusURIInvalid: return "URI Invalid"; case RPCStatusUpstreamFailed: return "Upstream Failed"; case RPCStatusSystemError: return "System Error. Use get_error() to get errno"; case RPCStatusSSLError: return "SSL Error. Use get_error() to get SSL-Error"; case RPCStatusDNSError: return "DNS Error. Use get_error() to get GAI-Error"; case RPCStatusProcessTerminated: return "Process Terminated"; default: return "Unknown Error"; } } void BRPCResponse::set_error(int error) { BrpcMeta *meta = static_cast(this->meta); meta->mutable_response()->set_error_code(error); } void BRPCResponse::set_correlation_id(int64_t cid) { BrpcMeta *meta = static_cast(this->meta); meta->set_correlation_id(cid); } int BRPCMessage::serialize(const ProtobufIDLMessage *pb_msg) { if (!pb_msg) return RPCStatusOK; BrpcMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int msg_len = pb_msg->ByteSizeLong(); RPCOutputStream stream(this->message, pb_msg->ByteSizeLong()); int ret = pb_msg->SerializeToZeroCopyStream(&stream) ? 0 : -1; if (ret < 0) return is_resp ? RPCStatusRespSerializeError : RPCStatusReqSerializeError; this->message_len = msg_len; return RPCStatusOK; } int BRPCMessage::deserialize(ProtobufIDLMessage *pb_msg) { const BrpcMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); RPCInputStream stream(this->message); if (pb_msg->ParseFromZeroCopyStream(&stream) == false) return is_resp ? RPCStatusRespDeserializeError : RPCStatusReqDeserializeError; return RPCStatusOK; } int BRPCMessage::compress() { BrpcMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int type = meta->compress_type(); size_t buflen = this->message_len; int status_code = RPCStatusOK; if (buflen == 0) return status_code; if (type == RPCCompressNone) return status_code; static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->lease_compressed_size(type, buflen); if (ret == -2) return is_resp ? RPCStatusReqCompressNotSupported : RPCStatusRespCompressNotSupported; else if (ret <= 0) return is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; //buflen = ret; RPCBuffer *dst_buf = new RPCBuffer(); ret = compressor->serialize_to_compressed(this->message, dst_buf, type); if (ret == -2) status_code = is_resp ? RPCStatusRespCompressNotSupported : RPCStatusReqCompressNotSupported; else if (ret == -1) status_code = is_resp ? RPCStatusRespCompressError : RPCStatusReqCompressError; else if (ret <= 0) status_code = is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; else buflen = ret; if (status_code == RPCStatusOK) { delete this->message; this->message = dst_buf; this->message_len = buflen; } else { delete dst_buf; } return status_code; } int BRPCMessage::decompress() { const BrpcMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int type = meta->compress_type(); int status_code = RPCStatusOK; if (this->message_len == 0 || type == RPCCompressNone) return status_code; RPCBuffer *dst_buf = new RPCBuffer(); static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->parse_from_compressed(this->message, dst_buf, type); if (ret == -2) status_code = is_resp ? RPCStatusRespDecompressNotSupported : RPCStatusReqDecompressNotSupported; else if (ret == -1) status_code = is_resp ? RPCStatusRespDecompressError : RPCStatusReqDecompressError; else if (ret <= 0) status_code = is_resp ? RPCStatusRespDecompressSizeInvalid : RPCStatusReqDecompressSizeInvalid; if (status_code == RPCStatusOK) { delete this->message; this->message = dst_buf; this->message_len = ret; } else { delete dst_buf; } return status_code; } inline int BRPCMessage::error_code_srpc_brpc(int srpc_status_code) const { switch (srpc_status_code) { case RPCStatusServiceNotFound: return BRPC_ENOSERVICE; case RPCStatusMethodNotFound: return BRPC_ENOMETHOD; case RPCStatusMetaError: case RPCStatusReqCompressSizeInvalid: case RPCStatusReqDecompressSizeInvalid: case RPCStatusReqCompressNotSupported: case RPCStatusReqDecompressNotSupported: case RPCStatusReqCompressError: case RPCStatusReqDecompressError: case RPCStatusReqSerializeError: case RPCStatusReqDeserializeError: return BRPC_EREQUEST; case RPCStatusRespCompressSizeInvalid: case RPCStatusRespDecompressSizeInvalid: case RPCStatusRespCompressNotSupported: case RPCStatusRespDecompressNotSupported: case RPCStatusRespCompressError: case RPCStatusRespDecompressError: case RPCStatusRespSerializeError: case RPCStatusRespDeserializeError: return BRPC_ERESPONSE; case RPCStatusProcessTerminated: return BRPC_ELOGOFF; default: return BRPC_EINTERNAL; } } inline int BRPCMessage::error_code_brpc_srpc(int brpc_error_code) const { switch (brpc_error_code) { case BRPC_ENOSERVICE: return RPCStatusServiceNotFound; case BRPC_ENOMETHOD: return RPCStatusMethodNotFound; case BRPC_EREQUEST: return RPCStatusReqDeserializeError; case BRPC_ERESPONSE: return RPCStatusRespDeserializeError; case BRPC_ELOGOFF: return RPCStatusProcessTerminated; default: return RPCStatusSystemError; } } } // namesapce sogou sogou-srpc-ac65014/src/message/rpc_message_brpc.h000066400000000000000000000147651525217424400220450ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_MESSAGE_BRPC_H__ #define __RPC_MESSAGE_BRPC_H__ #ifdef _WIN32 #include #else #include #endif #include "rpc_message.h" #include "rpc_basic.h" namespace srpc { static constexpr int BRPC_HEADER_SIZE = 12; class BRPCMessage : public RPCMessage { public: BRPCMessage(); virtual ~BRPCMessage(); int encode(struct iovec vectors[], int max, size_t size_limit); int append(const void *buf, size_t *size, size_t size_limit); int get_compress_type() const override; void set_compress_type(int type) override; bool get_attachment_nocopy(const char **attachment, size_t *len) const; void set_attachment_nocopy(const char *attachment, size_t len); int get_data_type() const override { return RPCDataProtobuf; } void set_data_type(int type) override { } //Different data type is not supported in BrpcMeta. bool get_meta_module_data(RPCModuleData& data) const override { return false; } bool set_meta_module_data(const RPCModuleData& data) override { return false; } public: using RPCMessage::serialize; using RPCMessage::deserialize; int serialize(const ProtobufIDLMessage *pb_msg) override; int deserialize(ProtobufIDLMessage *pb_msg) override; int compress() override; int decompress() override; protected: // "PRPC" + PAYLOAD_SIZE + META_SIZE char header[BRPC_HEADER_SIZE]; size_t nreceived; size_t meta_len; size_t message_len; size_t attachment_len; char *meta_buf; RPCBuffer *message; RPCBuffer *attachment; ProtobufIDLMessage *meta; protected: int error_code_srpc_brpc(int srpc_status_code) const; int error_code_brpc_srpc(int brpc_error_code) const; }; class BRPCRequest : public BRPCMessage { public: bool serialize_meta(); bool deserialize_meta(); const std::string& get_service_name() const; const std::string& get_method_name() const; void set_service_name(const std::string& service_name); void set_method_name(const std::string& method_name); int64_t get_correlation_id() const; }; class BRPCResponse : public BRPCMessage { public: bool serialize_meta(); bool deserialize_meta(); int get_status_code() const; int get_error() const; const char *get_errmsg() const; void set_status_code(int code); void set_error(int error); void set_correlation_id(int64_t cid); protected: int srpc_status_code = RPCStatusOK; std::string srpc_error_msg; }; class BRPCStdRequest : public protocol::ProtocolMessage, public RPCRequest, public BRPCRequest { public: int encode(struct iovec vectors[], int max) override { return this->BRPCRequest::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->BRPCRequest::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->BRPCRequest::serialize_meta(); } bool deserialize_meta() override { return this->BRPCRequest::deserialize_meta(); } public: const std::string& get_service_name() const override { return this->BRPCRequest::get_service_name(); } const std::string& get_method_name() const override { return this->BRPCRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->BRPCRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->BRPCRequest::set_method_name(method_name); } bool set_meta_module_data(const RPCModuleData& data) override { return this->BRPCMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->BRPCMessage::get_meta_module_data(data); } public: BRPCStdRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class BRPCStdResponse : public protocol::ProtocolMessage, public RPCResponse, public BRPCResponse { public: int encode(struct iovec vectors[], int max) override { return this->BRPCResponse::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->BRPCResponse::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->BRPCResponse::serialize_meta(); } bool deserialize_meta() override { return this->BRPCResponse::deserialize_meta(); } public: int get_status_code() const override { return this->BRPCResponse::get_status_code(); } int get_error() const override { return this->BRPCResponse::get_error(); } const char *get_errmsg() const override { return this->BRPCResponse::get_errmsg(); } void set_status_code(int code) override { return this->BRPCResponse::set_status_code(code); } void set_error(int error) override { return this->BRPCResponse::set_error(error); } bool set_meta_module_data(const RPCModuleData& data) override { return this->BRPCMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->BRPCMessage::get_meta_module_data(data); } public: BRPCStdResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; //////// // inl inline BRPCMessage::~BRPCMessage() { delete this->message; delete this->attachment; delete []this->meta_buf; delete this->meta; } inline int BRPCMessage::encode(struct iovec vectors[], int max, size_t size_limit) { size_t sz = this->meta_len + this->message_len + this->attachment_len; if (sz > 0x7FFFFFFF) { errno = EOVERFLOW; return -1; } int ret; int total; char *p = this->header; memcpy(p, "PRPC", 4); p += 4; *(uint32_t *)(p) = htonl((uint32_t)sz); p += 4; *(uint32_t *)(p) = htonl((uint32_t)this->meta_len); vectors[0].iov_base = this->header; vectors[0].iov_len = BRPC_HEADER_SIZE; vectors[1].iov_base = this->meta_buf; vectors[1].iov_len = this->meta_len; ret = this->message->encode(vectors + 2, max - 2); if (ret < 0) return ret; total = ret; if (this->attachment_len) { ret = this->attachment->encode(vectors + 2 + ret, max - 2 - ret); if (ret < 0) return ret; total += ret; } return 2 + total; } } // namespace srpc #endif sogou-srpc-ac65014/src/message/rpc_message_srpc.cc000066400000000000000000000641751525217424400222240ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include #include "rpc_basic.h" #include "rpc_compress.h" #include "rpc_meta.pb.h" #include "rpc_message_srpc.h" #include "rpc_zero_copy_stream.h" #include "rpc_module.h" #include "rpc_trace_module.h" namespace srpc { struct SRPCHttpHeadersString { const std::string RPCCompressType = "Content-Encoding"; const std::string OriginSize = "Origin-Size"; const std::string CompressdSize = "Content-Length"; const std::string DataType = "Content-Type"; const std::string SRPCStatus = "SRPC-Status"; const std::string SRPCError = "SRPC-Error"; }; struct CaseCmp { bool operator()(const std::string& lhs, const std::string& rhs) const { return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; } }; static const struct SRPCHttpHeadersString SRPCHttpHeaders; static const std::map SRPCHttpHeadersCode = { {SRPCHttpHeaders.RPCCompressType, 1}, {SRPCHttpHeaders.OriginSize, 2}, {SRPCHttpHeaders.CompressdSize, 3}, {SRPCHttpHeaders.DataType, 4}, {SRPCHttpHeaders.SRPCStatus, 5}, {SRPCHttpHeaders.SRPCError, 6} }; static const std::vector RPCDataTypeString = { "application/x-protobuf", "application/x-thrift", "application/json" }; static const std::vector RPCRPCCompressTypeString = { "identity", "x-snappy", "gzip", "deflate", "x-lz4" }; static constexpr const char *kTypePrefix = "type.googleapis.com"; class ResolverInstance { public: static google::protobuf::util::TypeResolver *get_resolver() { static ResolverInstance kInstance; return kInstance.resolver_; } private: ResolverInstance() { resolver_ = google::protobuf::util::NewTypeResolverForDescriptorPool( kTypePrefix, google::protobuf::DescriptorPool::generated_pool()); } ~ResolverInstance() { delete resolver_; } google::protobuf::util::TypeResolver *resolver_; }; static inline std::string GetTypeUrl(const ProtobufIDLMessage *pb_msg) { return kTypePrefix + ("/" + std::string(pb_msg->GetDescriptor()->full_name())); } SRPCMessage::SRPCMessage() { this->nreceived = 0; this->meta_buf = NULL; this->meta_len = 0; this->message_len = 0; memset(this->header, 0, sizeof (this->header)); this->meta = new RPCMeta(); this->buf = new RPCBuffer(); } int SRPCMessage::append(const void *buf, size_t *size, size_t size_limit) { uint32_t *p; size_t header_left, body_received, buf_len; if (this->nreceived < SRPC_HEADER_SIZE) { //receive header header_left = SRPC_HEADER_SIZE - this->nreceived; if (*size >= header_left) { //receive the whole header and ready to recieve body memcpy(this->header + this->nreceived, buf, header_left); this->nreceived += header_left; p = (uint32_t *)this->header + 1; this->meta_len = ntohl(*p); p = (uint32_t *)this->header + 2; this->message_len = ntohl(*p); buf_len = this->meta_len + this->message_len; if (buf_len >= size_limit) { errno = EMSGSIZE; return -1; } else if (buf_len > 0) { if (*size - header_left > buf_len) *size = header_left + buf_len; this->meta_buf = new char[this->meta_len]; if (*size - header_left <= this->meta_len) { memcpy(this->meta_buf, (const char *)buf + header_left, *size - header_left); } else { memcpy(this->meta_buf, (const char *)buf + header_left, this->meta_len); this->buf->append((const char *)buf + header_left + this->meta_len, *size - header_left - this->meta_len, BUFFER_MODE_COPY); } this->nreceived += *size - header_left; if (this->nreceived == SRPC_HEADER_SIZE + buf_len) return 1; else return 0; } else if (*size == header_left) { return 1; // means body_size == 0 and finish recieved header } else { // means buf_len < 0 errno = EBADMSG; return -1; } } else { // only receive header memcpy(this->header + this->nreceived, buf, *size); this->nreceived += *size; return 0; } } else { // have already received the header and now is for body only body_received = this->nreceived - SRPC_HEADER_SIZE; buf_len = this->meta_len + this->message_len; if (body_received + *size > buf_len) *size = buf_len - body_received; if (body_received + *size <= this->meta_len) { // 100 + 3 <= 106 memcpy(this->meta_buf + body_received, buf, *size); } else if (body_received < this->meta_len) { // 100 + ? > 106, 100 < 106 memcpy(this->meta_buf + body_received, buf, this->meta_len - body_received); if (body_received + *size > this->meta_len)// useless. always true // 100 + 10 > 106 this->buf->append((const char *)buf + this->meta_len - body_received, *size - this->meta_len + body_received, BUFFER_MODE_COPY); } else { // 110 > 106 this->buf->append((const char *)buf, *size, BUFFER_MODE_COPY); } this->nreceived += *size; return this->nreceived == SRPC_HEADER_SIZE + buf_len; } } int SRPCMessage::get_compress_type() const { RPCMeta *meta = static_cast(this->meta); return meta->compress_type(); } int SRPCMessage::get_data_type() const { RPCMeta *meta = static_cast(this->meta); return meta->data_type(); } void SRPCMessage::set_compress_type(int type) { RPCMeta *meta = static_cast(this->meta); meta->set_compress_type(type); } void SRPCMessage::set_data_type(int type) { RPCMeta *meta = static_cast(this->meta); meta->set_data_type(type); } void SRPCMessage::set_attachment_nocopy(const char *attachment, size_t len) { //TODO: } bool SRPCMessage::get_attachment_nocopy(const char **attachment, size_t *len) const { //TODO: return false; } bool SRPCMessage::set_meta_module_data(const RPCModuleData& data) { RPCMeta *meta = static_cast(this->meta); RPCMetaKeyValue *meta_kv; for (const auto& kv : data) { meta_kv = meta->add_trans_info(); if (kv.first == SRPC_TRACE_ID) { meta_kv->set_key(SRPC_TRACE_ID); meta_kv->set_bytes_value(kv.second.c_str(), SRPC_TRACEID_SIZE); } else if (kv.first == SRPC_SPAN_ID) { meta_kv->set_key(SRPC_SPAN_ID); meta_kv->set_bytes_value(kv.second.c_str(), SRPC_SPANID_SIZE); } else { meta_kv->set_key(kv.first); meta_kv->set_bytes_value(kv.second); } } return true; } bool SRPCMessage::get_meta_module_data(RPCModuleData& data) const { RPCMeta *meta = static_cast(this->meta); RPCMetaKeyValue *meta_kv; for (int i = 0; i < meta->trans_info_size(); i++) { meta_kv = meta->mutable_trans_info(i); if (meta_kv->key() == SRPC_TRACE_ID) data[SRPC_TRACE_ID] = meta_kv->bytes_value(); else if (meta_kv->key() == SRPC_SPAN_ID) data[SRPC_SPAN_ID] = meta_kv->bytes_value(); else if (meta_kv->key() == SRPC_PARENT_SPAN_ID) data[SRPC_PARENT_SPAN_ID] = meta_kv->bytes_value(); else data[meta_kv->key()] = meta_kv->bytes_value(); } return true; } const std::string& SRPCRequest::get_service_name() const { RPCMeta *meta = static_cast(this->meta); return meta->mutable_request()->service_name(); } const std::string& SRPCRequest::get_method_name() const { RPCMeta *meta = static_cast(this->meta); return meta->mutable_request()->method_name(); } void SRPCRequest::set_service_name(const std::string& service_name) { RPCMeta *meta = static_cast(this->meta); meta->mutable_request()->set_service_name(service_name); } void SRPCRequest::set_method_name(const std::string& method_name) { RPCMeta *meta = static_cast(this->meta); meta->mutable_request()->set_method_name(method_name); } int SRPCResponse::get_status_code() const { RPCMeta *meta = static_cast(this->meta); return meta->mutable_response()->status_code(); } void SRPCResponse::set_status_code(int code) { RPCMeta *meta = static_cast(this->meta); meta->mutable_response()->set_status_code(code); } int SRPCResponse::get_error() const { RPCMeta *meta = static_cast(this->meta); return meta->mutable_response()->error(); } const char *SRPCResponse::get_errmsg() const { switch (this->get_status_code()) { case RPCStatusOK: return "OK"; case RPCStatusUndefined: return "Undefined Error"; case RPCStatusServiceNotFound: return "Service Not Found"; case RPCStatusMethodNotFound: return "Method Not Found"; case RPCStatusMetaError: return "Meta Error"; case RPCStatusReqCompressSizeInvalid: return "Request Compress-size Invalid"; case RPCStatusReqDecompressSizeInvalid: return "Request Decompress-size Invalid"; case RPCStatusReqCompressNotSupported: return "Request Compress Not Supported"; case RPCStatusReqDecompressNotSupported: return "Request Decompress Not Supported"; case RPCStatusReqCompressError: return "Request Compress Error"; case RPCStatusReqDecompressError: return "Request Decompress Error"; case RPCStatusReqSerializeError: return "Request Serialize Error"; case RPCStatusReqDeserializeError: return "Request Deserialize Error"; case RPCStatusRespCompressSizeInvalid: return "Response Compress-size Invalid"; case RPCStatusRespDecompressSizeInvalid: return "Response Decompress-size Invalid"; case RPCStatusRespCompressNotSupported: return "Response Compress Not Supported"; case RPCStatusRespDecompressNotSupported: return "Response Decompress Not Supported"; case RPCStatusRespCompressError: return "Response Compress Error"; case RPCStatusRespDecompressError: return "Response Decompress Error"; case RPCStatusRespSerializeError: return "Response Serialize Error"; case RPCStatusRespDeserializeError: return "Response Deserialize Error"; case RPCStatusIDLSerializeNotSupported: return "IDL Serialize Not Supported"; case RPCStatusIDLDeserializeNotSupported: return "IDL Deserialize Not Supported"; case RPCStatusModuleFilterFailed: return "Module or filter check failed"; case RPCStatusURIInvalid: return "URI Invalid"; case RPCStatusUpstreamFailed: return "Upstream Failed"; case RPCStatusSystemError: return "System Error. Use get_error() to get errno"; case RPCStatusSSLError: return "SSL Error. Use get_error() to get SSL-Error"; case RPCStatusDNSError: return "DNS Error. Use get_error() to get GAI-Error"; case RPCStatusProcessTerminated: return "Process Terminated"; default: return "Unknown Error"; } } void SRPCResponse::set_error(int error) { RPCMeta *meta = static_cast(this->meta); meta->mutable_response()->set_error(error); } int SRPCMessage::serialize(const ProtobufIDLMessage *pb_msg) { using namespace google::protobuf; RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int data_type = meta->data_type(); int ret; if (!pb_msg) return RPCStatusOK; RPCOutputStream output_stream(this->buf, pb_msg->ByteSizeLong()); if (data_type == RPCDataProtobuf) { ret = pb_msg->SerializeToZeroCopyStream(&output_stream) ? 0 : -1; this->message_len = this->buf->size(); } else if (data_type == RPCDataJson) { std::string binary_input = pb_msg->SerializeAsString(); io::ArrayInputStream input_stream(binary_input.data(), (int)binary_input.size()); const auto *pool = pb_msg->GetDescriptor()->file()->pool(); auto *resolver = (pool == DescriptorPool::generated_pool() ? ResolverInstance::get_resolver() : util::NewTypeResolverForDescriptorPool(kTypePrefix, pool)); util::JsonPrintOptions options; options.add_whitespace = this->get_json_add_whitespace(); options.always_print_enums_as_ints = this->get_json_enums_as_ints(); options.preserve_proto_field_names = this->get_json_preserve_names(); #if GOOGLE_PROTOBUF_VERSION >= 5026000 options.always_print_fields_with_no_presence = this->get_json_fields_no_presence(); #else options.always_print_primitive_fields = this->get_json_fields_no_presence(); #endif ret = BinaryToJsonStream(resolver, GetTypeUrl(pb_msg), &input_stream, &output_stream, options).ok() ? 0 : -1; if (pool != DescriptorPool::generated_pool()) delete resolver; this->message_len = this->buf->size(); } else ret = -1; if (ret < 0) return is_resp ? RPCStatusRespSerializeError : RPCStatusReqSerializeError; return RPCStatusOK; } int SRPCMessage::deserialize(ProtobufIDLMessage *pb_msg) { using namespace google::protobuf; const RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int data_type = meta->data_type(); int ret; RPCInputStream input_stream(this->buf); if (data_type == RPCDataProtobuf) ret = pb_msg->ParseFromZeroCopyStream(&input_stream) ? 0 : -1; else if (data_type == RPCDataJson) { std::string binary_output; io::StringOutputStream output_stream(&binary_output); const auto *pool = pb_msg->GetDescriptor()->file()->pool(); auto *resolver = (pool == DescriptorPool::generated_pool() ? ResolverInstance::get_resolver() : util::NewTypeResolverForDescriptorPool(kTypePrefix, pool)); util::JsonParseOptions options; options.ignore_unknown_fields = true; if (JsonToBinaryStream(resolver, GetTypeUrl(pb_msg), &input_stream, &output_stream, options).ok()) { ret = pb_msg->ParseFromString(binary_output) ? 0 : -1; } else ret = -1; if (pool != DescriptorPool::generated_pool()) delete resolver; } else ret = -1; if (ret < 0) return is_resp ? RPCStatusRespDeserializeError : RPCStatusReqDeserializeError; return RPCStatusOK; } int SRPCMessage::serialize(const ThriftIDLMessage *thrift_msg) { RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int data_type = meta->data_type(); int ret; if (!thrift_msg) return RPCStatusOK; ThriftBuffer thrift_buffer(this->buf); if (data_type == RPCDataThrift) ret = thrift_msg->descriptor->writer(thrift_msg, &thrift_buffer) ? 0 : -1; else if (data_type == RPCDataJson) ret = thrift_msg->descriptor->json_writer(thrift_msg, &thrift_buffer) ? 0 : -1; else ret = -1; if (ret < 0) return is_resp ? RPCStatusRespSerializeError : RPCStatusReqSerializeError; this->message_len = this->buf->size(); return RPCStatusOK; } int SRPCMessage::deserialize(ThriftIDLMessage *thrift_msg) { const RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int data_type = meta->data_type(); int ret; if (this->buf->size() == 0 || this->message_len == 0) return is_resp ? RPCStatusRespDeserializeError : RPCStatusReqDeserializeError; ThriftBuffer thrift_buffer(this->buf); if (data_type == RPCDataThrift) ret = thrift_msg->descriptor->reader(&thrift_buffer, thrift_msg) ? 0 : 1; else if (data_type == RPCDataJson) ret = thrift_msg->descriptor->json_reader(&thrift_buffer, thrift_msg) ? 0 : 1; else ret = -1; if (ret < 0) return is_resp ? RPCStatusRespDeserializeError : RPCStatusReqDeserializeError; return RPCStatusOK; } int SRPCMessage::compress() { RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int type = meta->compress_type(); size_t buflen = this->message_len; int origin_size; int status_code = RPCStatusOK; if (buflen == 0) { if (type != RPCCompressNone) { meta->set_origin_size(0); meta->set_compressed_size(0); } return status_code; } if (type == RPCCompressNone) return status_code; if (buflen > 0x7FFFFFFF) { return is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; } origin_size = (int)buflen; static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->lease_compressed_size(type, buflen); if (ret == -2) { return is_resp ? RPCStatusReqCompressNotSupported : RPCStatusRespCompressNotSupported; } else if (ret <= 0) { return is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; } buflen = ret; if (this->buf->size() != this->message_len) return is_resp ? RPCStatusRespCompressError : RPCStatusReqCompressError; RPCBuffer *dst_buf = new RPCBuffer(); ret = compressor->serialize_to_compressed(this->buf, dst_buf, type); if (ret == -2) { status_code = is_resp ? RPCStatusRespCompressNotSupported : RPCStatusReqCompressNotSupported; } else if (ret == -1) { status_code = is_resp ? RPCStatusRespCompressError : RPCStatusReqCompressError; } else if (ret <= 0) { status_code = is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; } else { meta->set_origin_size(origin_size); meta->set_compressed_size(ret); buflen = ret; } if (status_code == RPCStatusOK) { delete this->buf; this->buf = dst_buf; this->message_len = buflen; } else delete dst_buf; return status_code; } int SRPCMessage::decompress() { const RPCMeta *meta = static_cast(this->meta); bool is_resp = !meta->has_request(); int type = meta->compress_type(); int status_code = RPCStatusOK; if (this->message_len == 0 || type == RPCCompressNone) return status_code; if (meta->compressed_size() == 0) { return is_resp ? RPCStatusRespDecompressSizeInvalid : RPCStatusReqDecompressSizeInvalid; } if (this->buf->size() != (size_t)meta->compressed_size()) return is_resp ? RPCStatusRespCompressError : RPCStatusReqCompressError; RPCBuffer *dst_buf = new RPCBuffer(); static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->parse_from_compressed(this->buf, dst_buf, type); if (ret == -2) { status_code = is_resp ? RPCStatusRespDecompressNotSupported : RPCStatusReqDecompressNotSupported; } else if (ret == -1) { status_code = is_resp ? RPCStatusRespDecompressError : RPCStatusReqDecompressError; } else if (ret <= 0 || (meta->has_origin_size() && ret != meta->origin_size())) { status_code = is_resp ? RPCStatusRespDecompressSizeInvalid : RPCStatusReqDecompressSizeInvalid; } if (status_code == RPCStatusOK) { delete this->buf; this->buf = dst_buf; this->message_len = ret; } else delete dst_buf; return status_code; } static bool __deserialize_meta_http(const char *request_uri, protocol::HttpMessage *http_msg, SRPCMessage *srpc_msg, ProtobufIDLMessage *pb_msg) { RPCMeta *meta = static_cast(pb_msg); protocol::HttpHeaderCursor header_cursor(http_msg); std::string key, value; while (header_cursor.next(key, value)) { const auto it = SRPCHttpHeadersCode.find(key); if (it != SRPCHttpHeadersCode.cend()) { switch (it->second) { case 1: for (size_t i = 0; i < RPCRPCCompressTypeString.size(); i++) { if (strcasecmp(RPCRPCCompressTypeString[i].c_str(), value.c_str()) == 0) { meta->set_compress_type(i); break; } } break; case 2: meta->set_origin_size(atoi(value.c_str())); break; case 4: for (size_t i = 0; i < RPCDataTypeString.size(); i++) { if (strcasecmp(RPCDataTypeString[i].c_str(), value.c_str()) == 0) { meta->set_data_type(i); break; } } break; default: continue; } } } if (request_uri && request_uri[0] == '/') { std::string str = request_uri + 1; auto pos = str.find_first_of("?#"); if (pos != std::string::npos) str.erase(pos); if (!str.empty() && str.back() == '/') str.pop_back(); for (char& c : str) { if (c == '/') c = '.'; } pos = str.find_last_of('.'); if (pos != std::string::npos) { meta->mutable_request()->set_service_name(str.substr(0, pos)); meta->mutable_request()->set_method_name(str.substr(pos + 1)); } } const void *ptr; size_t len; http_msg->get_parsed_body(&ptr, &len); if (len > 0x7FFFFFFF) return false; protocol::HttpChunkCursor chunk_cursor(http_msg); RPCBuffer *buf = srpc_msg->get_buffer(); size_t msg_len = 0; while (chunk_cursor.next(&ptr, &len)) { buf->append((const char *)ptr, len, BUFFER_MODE_NOCOPY); msg_len += len; } srpc_msg->set_message_len(msg_len); if (!meta->has_data_type()) meta->set_data_type(RPCDataJson); if (!meta->has_compress_type()) meta->set_compress_type(RPCCompressNone); if (meta->compress_type() == RPCCompressNone) { if (msg_len == 0 && meta->data_type() == RPCDataJson) { buf->append("{}", 2, BUFFER_MODE_NOCOPY); srpc_msg->set_message_len(2); } } else meta->set_compressed_size(msg_len); return true; } bool SRPCHttpRequest::serialize_meta() { if (this->buf->size() > 0x7FFFFFFF) return false; RPCMeta *meta = static_cast(this->meta); int data_type = meta->data_type(); int compress_type = meta->compress_type(); set_http_version("HTTP/1.1"); set_method("POST"); set_request_uri("/" + meta->mutable_request()->service_name() + "/" + meta->mutable_request()->method_name()); //set header set_header_pair(SRPCHttpHeaders.DataType, RPCDataTypeString[data_type]); set_header_pair(SRPCHttpHeaders.RPCCompressType, RPCRPCCompressTypeString[compress_type]); if (compress_type != RPCCompressNone) { set_header_pair(SRPCHttpHeaders.CompressdSize, std::to_string(meta->compressed_size())); set_header_pair(SRPCHttpHeaders.OriginSize, std::to_string(meta->origin_size())); } else { set_header_pair("Content-Length", std::to_string(this->message_len)); } set_header_pair("Connection", "Keep-Alive"); const void *buffer; size_t buflen; //append body while (buflen = this->buf->fetch(&buffer), buffer && buflen > 0) this->append_output_body_nocopy(buffer, buflen); return true; } bool SRPCHttpRequest::deserialize_meta() { const char *request_uri = this->get_request_uri(); return __deserialize_meta_http(request_uri, this, this, this->meta); } bool SRPCHttpResponse::serialize_meta() { if (this->buf->size() > 0x7FFFFFFF) return false; RPCMeta *meta = static_cast(this->meta); int data_type = meta->data_type(); int compress_type = meta->compress_type(); int rpc_status_code = this->get_status_code(); const char *http_status_code = this->protocol::HttpResponse::get_status_code(); set_http_version("HTTP/1.1"); if (rpc_status_code == RPCStatusOK) { if (http_status_code) protocol::HttpUtil::set_response_status(this, atoi(http_status_code)); else protocol::HttpUtil::set_response_status(this, HttpStatusOK); } else if (rpc_status_code == RPCStatusServiceNotFound || rpc_status_code == RPCStatusMethodNotFound || rpc_status_code == RPCStatusMetaError || rpc_status_code == RPCStatusURIInvalid) { protocol::HttpUtil::set_response_status(this, HttpStatusBadRequest); } else if (rpc_status_code == RPCStatusRespCompressNotSupported || rpc_status_code == RPCStatusRespDecompressNotSupported || rpc_status_code == RPCStatusIDLSerializeNotSupported || rpc_status_code == RPCStatusIDLDeserializeNotSupported) { protocol::HttpUtil::set_response_status(this, HttpStatusNotImplemented); } else if (rpc_status_code == RPCStatusUpstreamFailed) { protocol::HttpUtil::set_response_status(this, HttpStatusServiceUnavailable); } else { protocol::HttpUtil::set_response_status(this, HttpStatusInternalServerError); } //set header set_header_pair(SRPCHttpHeaders.SRPCStatus, std::to_string(meta->mutable_response()->status_code())); set_header_pair(SRPCHttpHeaders.SRPCError, std::to_string(meta->mutable_response()->error())); set_header_pair(SRPCHttpHeaders.DataType, RPCDataTypeString[data_type]); set_header_pair(SRPCHttpHeaders.RPCCompressType, RPCRPCCompressTypeString[compress_type]); if (compress_type != RPCCompressNone) { set_header_pair(SRPCHttpHeaders.CompressdSize, std::to_string(meta->compressed_size())); set_header_pair(SRPCHttpHeaders.OriginSize, std::to_string(meta->origin_size())); } else { set_header_pair("Content-Length", std::to_string(this->message_len)); } set_header_pair("Connection", "Keep-Alive"); const void *buffer; size_t buflen; //append body while (buflen = this->buf->fetch(&buffer), buffer && buflen > 0) this->append_output_body_nocopy(buffer, buflen); return true; } bool SRPCHttpResponse::deserialize_meta() { return __deserialize_meta_http(NULL, this, this, this->meta); } bool SRPCHttpRequest::set_meta_module_data(const RPCModuleData& data) { return http_set_header_module_data(data, this); } bool SRPCHttpRequest::get_meta_module_data(RPCModuleData& data) const { return http_get_header_module_data(this, data); } bool SRPCHttpResponse::set_meta_module_data(const RPCModuleData& data) { return http_set_header_module_data(data, this); } bool SRPCHttpResponse::get_meta_module_data(RPCModuleData& data) const { return http_get_header_module_data(this, data); } bool SRPCHttpRequest::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool SRPCHttpRequest::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool SRPCHttpRequest::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } bool SRPCHttpResponse::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool SRPCHttpResponse::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool SRPCHttpResponse::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } } // namespace srpc sogou-srpc-ac65014/src/message/rpc_message_srpc.h000066400000000000000000000214761525217424400220630ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_MESSAGE_SRPC_H__ #define __RPC_MESSAGE_SRPC_H__ #ifdef _WIN32 #include #else #include #endif #include #include "rpc_message.h" #include "rpc_basic.h" #include "rpc_thrift_idl.h" #include "rpc_buffer.h" namespace srpc { static constexpr int SRPC_HEADER_SIZE = 16; // define srpc protocol class SRPCMessage : public RPCMessage { public: SRPCMessage(); virtual ~SRPCMessage(); int encode(struct iovec vectors[], int max, size_t size_limit); int append(const void *buf, size_t *size, size_t size_limit); bool serialize_meta(); bool deserialize_meta(); int get_compress_type() const override; int get_data_type() const override; void set_compress_type(int type) override; void set_data_type(int type) override; void set_attachment_nocopy(const char *attachment, size_t len); bool get_attachment_nocopy(const char **attachment, size_t *len) const; bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; public: using RPCMessage::serialize; using RPCMessage::deserialize; int serialize(const ProtobufIDLMessage *pb_msg) override; int deserialize(ProtobufIDLMessage *pb_msg) override; int serialize(const ThriftIDLMessage *thrift_msg) override; int deserialize(ThriftIDLMessage *thrift_msg) override; int compress() override; int decompress() override; public: RPCBuffer *get_buffer() const { return this->buf; } size_t get_message_len() const { return this->message_len; } void set_message_len(size_t len) { this->message_len = len; } protected: void init_meta(); // "SRPC" + META_LEN + MESSAGE_LEN + RESERVED char header[SRPC_HEADER_SIZE]; RPCBuffer *buf; char *meta_buf; size_t nreceived; size_t meta_len; size_t message_len; ProtobufIDLMessage *meta; }; class SRPCRequest : public SRPCMessage { public: const std::string& get_service_name() const; const std::string& get_method_name() const; void set_service_name(const std::string& service_name); void set_method_name(const std::string& method_name); }; class SRPCResponse : public SRPCMessage { public: int get_status_code() const; int get_error() const; const char *get_errmsg() const; void set_status_code(int code); void set_error(int error); }; class SRPCStdRequest : public protocol::ProtocolMessage, public RPCRequest, public SRPCRequest { public: int encode(struct iovec vectors[], int max) override { return this->SRPCRequest::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->SRPCRequest::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->SRPCRequest::serialize_meta(); } bool deserialize_meta() override { return this->SRPCRequest::deserialize_meta(); } public: const std::string& get_service_name() const override { return this->SRPCRequest::get_service_name(); } const std::string& get_method_name() const override { return this->SRPCRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->SRPCRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->SRPCRequest::set_method_name(method_name); } bool set_meta_module_data(const RPCModuleData& data) override { return this->SRPCMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->SRPCMessage::get_meta_module_data(data); } public: SRPCStdRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class SRPCStdResponse : public protocol::ProtocolMessage, public RPCResponse, public SRPCResponse { public: int encode(struct iovec vectors[], int max) override { return this->SRPCResponse::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->SRPCResponse::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->SRPCResponse::serialize_meta(); } bool deserialize_meta() override { return this->SRPCResponse::deserialize_meta(); } public: int get_status_code() const override { return this->SRPCResponse::get_status_code(); } int get_error() const override { return this->SRPCResponse::get_error(); } const char *get_errmsg() const override { return this->SRPCResponse::get_errmsg(); } void set_status_code(int code) override { return this->SRPCResponse::set_status_code(code); } void set_error(int error) override { return this->SRPCResponse::set_error(error); } bool set_meta_module_data(const RPCModuleData& data) override { return this->SRPCMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->SRPCMessage::get_meta_module_data(data); } public: SRPCStdResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class SRPCHttpRequest : public protocol::HttpRequest, public RPCRequest, public SRPCRequest { public: bool serialize_meta() override; bool deserialize_meta() override; public: const std::string& get_service_name() const override { return this->SRPCRequest::get_service_name(); } const std::string& get_method_name() const override { return this->SRPCRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->SRPCRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->SRPCRequest::set_method_name(method_name); } bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; public: SRPCHttpRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class SRPCHttpResponse : public protocol::HttpResponse, public RPCResponse, public SRPCResponse { public: bool serialize_meta() override; bool deserialize_meta() override; public: int get_status_code() const override { return this->SRPCResponse::get_status_code(); } int get_error() const override { return this->SRPCResponse::get_error(); } const char *get_errmsg() const override { return this->SRPCResponse::get_errmsg(); } void set_status_code(int code) override { return this->SRPCResponse::set_status_code(code); } void set_error(int error) override { return this->SRPCResponse::set_error(error); } bool set_http_code(int code) override { return this->protocol::HttpResponse::set_status_code(std::to_string(code)); } bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; public: SRPCHttpResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; //////// // inl inline SRPCMessage::~SRPCMessage() { delete []this->meta_buf; delete this->meta; delete this->buf; } inline int SRPCMessage::encode(struct iovec vectors[], int max, size_t size_limit) { if (this->message_len > 0x7FFFFFFF) { errno = EOVERFLOW; return -1; } char *p = this->header; memcpy(p, "SRPC", 4); p += 4; *(uint32_t *)(p) = htonl((uint32_t)this->meta_len); p += 4; *(uint32_t *)(p) = htonl((uint32_t)this->message_len); vectors[0].iov_base = this->header; vectors[0].iov_len = SRPC_HEADER_SIZE; vectors[1].iov_base = this->meta_buf; vectors[1].iov_len = this->meta_len; int ret = this->buf->encode(vectors + 2, max - 2); return ret < 0 ? ret : ret + 2; } inline bool SRPCMessage::serialize_meta() { this->meta_len = this->meta->ByteSizeLong(); this->meta_buf = new char[this->meta_len]; return this->meta->SerializeToArray(this->meta_buf, (int)this->meta_len); } inline bool SRPCMessage::deserialize_meta() { return this->meta->ParseFromArray(this->meta_buf, (int)this->meta_len); } } // namespace srpc #endif sogou-srpc-ac65014/src/message/rpc_message_thrift.cc000066400000000000000000000215701525217424400225450ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include #include "rpc_message_thrift.h" namespace srpc { static int thrift_parser_append_message(const void *buf, size_t *size, ThriftBuffer *TBuffer) { if (TBuffer->status == THRIFT_PARSE_END) { *size = 0; return 1; } /* if (TBuffer->status == THRIFT_PARSE_INIT) TBuffer->status = THRIFT_GET_FRAME_SIZE; */ if (TBuffer->status == THRIFT_GET_FRAME_SIZE) { size_t framesize_bytelen = sizeof (TBuffer->framesize); char *readbuf = (char*)&TBuffer->framesize; size_t read_size = 0; for (size_t i = 0; i < *size; i++) { read_size++; ((char *)readbuf)[TBuffer->framesize_read_byte] = ((char *)buf)[i]; if (++TBuffer->framesize_read_byte == framesize_bytelen) { TBuffer->status = THRIFT_GET_DATA; TBuffer->framesize = ntohl(TBuffer->framesize); if (TBuffer->framesize < 0) { errno = EBADMSG; return -1; } //TBuffer->readbuf = new char[TBuffer->framesize]; break; } } size_t left_size = *size - read_size; *size = read_size; if (left_size == 0) { if (TBuffer->status == THRIFT_GET_DATA && TBuffer->framesize == 0) { TBuffer->status = THRIFT_PARSE_END; return 1; } else return 0; } else { int ret = thrift_parser_append_message((char *)buf + read_size, &left_size, TBuffer); *size += left_size; return ret; } } else if (TBuffer->status == THRIFT_GET_DATA) { size_t read_size = *size; if (TBuffer->readbuf_size + *size > (size_t)TBuffer->framesize) read_size = TBuffer->framesize - TBuffer->readbuf_size; TBuffer->buffer->append((const char *)buf, read_size, BUFFER_MODE_COPY); TBuffer->readbuf_size += read_size; *size = read_size; if (TBuffer->readbuf_size < (size_t)TBuffer->framesize) return 0; else if (TBuffer->readbuf_size == (uint32_t)TBuffer->framesize) { TBuffer->status = THRIFT_PARSE_END; return 1; } } errno = EBADMSG; return -1; } int ThriftMessage::append(const void *buf, size_t *size, size_t size_limit) { return thrift_parser_append_message(buf, size, &TBuffer_); } bool ThriftResponse::serialize_meta() { if (status_code_ == RPCStatusOK) TBuffer_.meta.message_type = TMT_REPLY; else { ThriftException ex; ex.type = (status_code_ == RPCStatusMethodNotFound ? TET_UNKNOWN_METHOD : TET_UNKNOWN); ex.message = errmsg_; ex.descriptor->writer(&ex, &TBuffer_); TBuffer_.meta.message_type = TMT_EXCEPTION; } return TBuffer_.writeMessageBegin(); } const char *thrift_error2errmsg(int error) { switch (error) { case TET_UNKNOWN: return "TApplicationException: Unknown application exception"; case TET_UNKNOWN_METHOD: return "TApplicationException: Unknown method"; case TET_INVALID_MESSAGE_TYPE: return "TApplicationException: Invalid message type"; case TET_WRONG_METHOD_NAME: return "TApplicationException: Wrong method name"; case TET_BAD_SEQUENCE_ID: return "TApplicationException: Bad sequence identifier"; case TET_MISSING_RESULT: return "TApplicationException: Missing result"; case TET_INTERNAL_ERROR: return "TApplicationException: Internal error"; case TET_PROTOCOL_ERROR: return "TApplicationException: Protocol error"; case TET_INVALID_TRANSFORM: return "TApplicationException: Invalid transform"; case TET_INVALID_PROTOCOL: return "TApplicationException: Invalid protocol"; case TET_UNSUPPORTED_CLIENT_TYPE: return "TApplicationException: Unsupported client type"; default: return "TApplicationException: (Invalid exception type)"; }; } bool ThriftResponse::deserialize_meta() { if (TBuffer_.readMessageBegin()) { if (TBuffer_.meta.message_type == TMT_EXCEPTION) { ThriftException ex; if (ex.descriptor->reader(&TBuffer_, &ex)) { status_code_ = (ex.type == TET_UNKNOWN_METHOD ? RPCStatusMethodNotFound : RPCStatusMetaError); error_ = ex.type; errmsg_ = ex.message; } else { status_code_ = RPCStatusMetaError; error_ = TET_INTERNAL_ERROR; errmsg_ = thrift_error2errmsg(error_); } } return true; } return false; } const char *ThriftResponse::get_errmsg() const { if (!errmsg_.empty()) return errmsg_.c_str(); return thrift_error2errmsg(error_); } bool ThriftHttpRequest::serialize_meta() { if (buf_.size() > 0x7FFFFFFF) return false; if (!this->ThriftRequest::serialize_meta()) return false; set_http_version("HTTP/1.1"); set_method("POST"); set_request_uri("/"); set_header_pair("Connection", "Keep-Alive"); set_header_pair("Content-Type", "application/x-thrift"); set_header_pair("Content-Length", std::to_string(TBuffer_.meta.writebuf.size() + buf_.size())); this->append_output_body_nocopy(TBuffer_.meta.writebuf.c_str(), TBuffer_.meta.writebuf.size()); const void *buf; size_t buflen; while (buflen = buf_.fetch(&buf), buf && buflen > 0) this->append_output_body_nocopy(buf, buflen); return true; } bool ThriftHttpRequest::deserialize_meta() { const void *body; size_t body_len; get_parsed_body(&body, &body_len); if (body_len > 0x7FFFFFFF) return false; buf_.append((const char *)body, body_len, BUFFER_MODE_NOCOPY); TBuffer_.framesize = (int32_t)body_len; return this->ThriftRequest::deserialize_meta(); } bool ThriftHttpResponse::serialize_meta() { if (buf_.size() > 0x7FFFFFFF) return false; if (!this->ThriftResponse::serialize_meta()) return false; int rpc_status_code = this->get_status_code(); const char *http_status_code = this->protocol::HttpResponse::get_status_code(); set_http_version("HTTP/1.1"); if (rpc_status_code == RPCStatusOK) { if (http_status_code) protocol::HttpUtil::set_response_status(this, atoi(http_status_code)); else protocol::HttpUtil::set_response_status(this, HttpStatusOK); } else if (rpc_status_code == RPCStatusServiceNotFound || rpc_status_code == RPCStatusMethodNotFound || rpc_status_code == RPCStatusMetaError || rpc_status_code == RPCStatusURIInvalid) protocol::HttpUtil::set_response_status(this, HttpStatusBadRequest); else if (rpc_status_code == RPCStatusRespCompressNotSupported || rpc_status_code == RPCStatusRespDecompressNotSupported || rpc_status_code == RPCStatusIDLSerializeNotSupported || rpc_status_code == RPCStatusIDLDeserializeNotSupported) protocol::HttpUtil::set_response_status(this, HttpStatusNotImplemented); else if (rpc_status_code == RPCStatusUpstreamFailed) protocol::HttpUtil::set_response_status(this, HttpStatusServiceUnavailable); else protocol::HttpUtil::set_response_status(this, HttpStatusInternalServerError); set_header_pair("Connection", "Keep-Alive"); set_header_pair("Content-Type", "application/x-thrift"); set_header_pair("Content-Length", std::to_string(TBuffer_.meta.writebuf.size() + buf_.size())); this->append_output_body_nocopy(TBuffer_.meta.writebuf.c_str(), TBuffer_.meta.writebuf.size()); const void *buf; size_t buflen; while (buflen = buf_.fetch(&buf), buf && buflen > 0) this->append_output_body_nocopy(buf, buflen); return true; } bool ThriftHttpResponse::deserialize_meta() { const void *body; size_t body_len; get_parsed_body(&body, &body_len); if (body_len > 0x7FFFFFFF) return false; buf_.append((const char *)body, body_len, BUFFER_MODE_NOCOPY); TBuffer_.framesize = (int32_t)body_len; return this->ThriftResponse::deserialize_meta(); } bool ThriftHttpRequest::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool ThriftHttpRequest::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool ThriftHttpRequest::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } bool ThriftHttpResponse::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool ThriftHttpResponse::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool ThriftHttpResponse::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } } // namespace srpc sogou-srpc-ac65014/src/message/rpc_message_thrift.h000066400000000000000000000262311525217424400224060ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #ifndef __RPC_MESSAGE_THRIFT_H__ #define __RPC_MESSAGE_THRIFT_H__ #include #include #include "rpc_message.h" #include "rpc_thrift_idl.h" namespace srpc { class ThriftException : public ThriftIDLMessage { public: std::string message; int32_t type; public: struct ISSET { bool message = true; bool type = true; }__isset; ThriftException() { this->elements = ThriftElementsImpl::get_elements_instance(); this->descriptor = ThriftDescriptorImpl::get_instance(); } static void StaticElementsImpl(std::list *elements) { const ThriftException *st = (const ThriftException *)0; const char *base = (const char *)st; using subtype_1 = ThriftDescriptorImpl; using subtype_2 = ThriftDescriptorImpl; elements->push_back({subtype_1::get_instance(), "message", (const char *)(&st->__isset.message) - base, (const char *)(&st->message) - base, 1}); elements->push_back({subtype_2::get_instance(), "type", (const char *)(&st->__isset.type) - base, (const char *)(&st->type) - base, 2}); } }; class ThriftMessage : public RPCMessage { public: ThriftMessage() : TBuffer_(&buf_) { } virtual ~ThriftMessage() { } //copy constructor ThriftMessage(const ThriftMessage&) = delete; //copy operator ThriftMessage& operator= (const ThriftMessage&) = delete; //move constructor ThriftMessage(ThriftMessage&&) = delete; //move operator ThriftMessage& operator= (ThriftMessage&&) = delete; public: int get_compress_type() const override { return RPCCompressNone; } int get_data_type() const override { return RPCDataThrift; } void set_compress_type(int type) override {} void set_data_type(int type) override {} void set_attachment_nocopy(const char *attachment, size_t len) { } bool get_attachment_nocopy(const char **attachment, size_t *len) const { return false; } public: int serialize(const ThriftIDLMessage *thrift_msg) override; int deserialize(ThriftIDLMessage *thrift_msg) override; int compress() override { return RPCStatusOK; } int decompress() override { return RPCStatusOK; } bool get_meta_module_data(RPCModuleData& data) const override { return false; } bool set_meta_module_data(const RPCModuleData& data) override { return false; } public: const ThriftMeta *get_meta() const { return &TBuffer_.meta; } ThriftMeta *get_meta() { return &TBuffer_.meta; } protected: int encode(struct iovec vectors[], int max, size_t size_limit); int append(const void *buf, size_t *size, size_t size_limit); RPCBuffer buf_; ThriftBuffer TBuffer_; }; class ThriftRequest : public ThriftMessage { public: bool serialize_meta() { return TBuffer_.writeMessageBegin(); } bool deserialize_meta() { return TBuffer_.readMessageBegin(); } public: const std::string& get_service_name() const { return TBuffer_.meta.method_name; } const std::string& get_method_name() const { return TBuffer_.meta.method_name; } void set_service_name(const std::string& service_name) { } void set_method_name(const std::string& method_name) { TBuffer_.meta.method_name = method_name; } void set_seqid(long long seqid) { TBuffer_.meta.seqid = (int)seqid; } }; class ThriftResponse : public ThriftMessage { public: bool serialize_meta(); bool deserialize_meta(); bool get_meta_module_data(RPCModuleData& data) const override { return false; } bool set_meta_module_data(const RPCModuleData& data) override { return false; } public: int get_status_code() const { return status_code_; } int get_error() const { return error_; } const char *get_errmsg() const; void set_status_code(int code) { status_code_ = code; } void set_error(int error) { error_ = error; } protected: int status_code_ = RPCStatusOK; int error_ = TET_UNKNOWN; std::string errmsg_; }; class ThriftStdRequest : public protocol::ProtocolMessage, public RPCRequest, public ThriftRequest { public: int encode(struct iovec vectors[], int max) override { return this->ThriftRequest::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->ThriftRequest::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->ThriftRequest::serialize_meta(); } bool deserialize_meta() override { return this->ThriftRequest::deserialize_meta(); } public: const std::string& get_service_name() const override { return this->ThriftRequest::get_service_name(); } const std::string& get_method_name() const override { return this->ThriftRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->ThriftRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->ThriftRequest::set_method_name(method_name); } void set_seqid(long long seqid) override { this->ThriftRequest::set_seqid(seqid); } bool set_meta_module_data(const RPCModuleData& data) override { return this->ThriftMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->ThriftMessage::get_meta_module_data(data); } public: ThriftStdRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class ThriftStdResponse : public protocol::ProtocolMessage, public RPCResponse, public ThriftResponse { public: int encode(struct iovec vectors[], int max) override { return this->ThriftResponse::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->ThriftResponse::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->ThriftResponse::serialize_meta(); } bool deserialize_meta() override { return this->ThriftResponse::deserialize_meta(); } public: int get_status_code() const override { return this->ThriftResponse::get_status_code(); } int get_error() const override { return this->ThriftResponse::get_error(); } const char *get_errmsg() const override { return this->ThriftResponse::get_errmsg(); } void set_status_code(int code) override { return this->ThriftResponse::set_status_code(code); } void set_error(int error) override { return this->ThriftResponse::set_error(error); } bool set_meta_module_data(const RPCModuleData& data) override { return this->ThriftMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->ThriftMessage::get_meta_module_data(data); } public: ThriftStdResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class ThriftHttpRequest : public protocol::HttpRequest, public RPCRequest, public ThriftRequest { public: bool serialize_meta() override; bool deserialize_meta() override; public: const std::string& get_service_name() const override { return this->ThriftRequest::get_service_name(); } const std::string& get_method_name() const override { return this->ThriftRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->ThriftRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->ThriftRequest::set_method_name(method_name); } void set_seqid(long long seqid) override { this->ThriftRequest::set_seqid(seqid); } bool set_meta_module_data(const RPCModuleData& data) override { return this->ThriftMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->ThriftMessage::get_meta_module_data(data); } bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; public: ThriftHttpRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class ThriftHttpResponse : public protocol::HttpResponse, public RPCResponse, public ThriftResponse { public: bool serialize_meta() override; bool deserialize_meta() override; public: int get_status_code() const override { return this->ThriftResponse::get_status_code(); } int get_error() const override { return this->ThriftResponse::get_error(); } const char *get_errmsg() const override { return this->ThriftResponse::get_errmsg(); } void set_status_code(int code) override { return this->ThriftResponse::set_status_code(code); } void set_error(int error) override { return this->ThriftResponse::set_error(error); } bool set_http_code(int code) override { return this->protocol::HttpResponse::set_status_code(std::to_string(code)); } bool set_meta_module_data(const RPCModuleData& data) override { return this->ThriftMessage::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->ThriftMessage::get_meta_module_data(data); } bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; public: ThriftHttpResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; //////// // inl inline int ThriftMessage::serialize(const ThriftIDLMessage *thrift_msg) { if (thrift_msg) { if (!thrift_msg->descriptor->writer(thrift_msg, &TBuffer_)) { return TBuffer_.meta.message_type == TMT_CALL ? RPCStatusReqSerializeError : RPCStatusRespSerializeError; } } return RPCStatusOK; } inline int ThriftMessage::deserialize(ThriftIDLMessage *thrift_msg) { if (thrift_msg) { if (!thrift_msg->descriptor->reader(&TBuffer_, thrift_msg)) { return TBuffer_.meta.message_type == TMT_CALL ? RPCStatusReqDeserializeError : RPCStatusRespDeserializeError; } } return RPCStatusOK; } inline int ThriftMessage::encode(struct iovec vectors[], int max, size_t size_limit) { size_t sz = TBuffer_.meta.writebuf.size() + buf_.size(); if (sz > 0x7FFFFFFF) { errno = EOVERFLOW; return -1; } if (sz > 0) { TBuffer_.framesize = ntohl((int32_t)sz); vectors[0].iov_base = (char *)&TBuffer_.framesize; vectors[0].iov_len = sizeof (int32_t); vectors[1].iov_base = const_cast(TBuffer_.meta.writebuf.c_str()); vectors[1].iov_len = TBuffer_.meta.writebuf.size(); int ret = buf_.encode(vectors + 2, max - 2); return ret < 0 ? ret : 2 + ret; } return 0; } } // namespace srpc #endif sogou-srpc-ac65014/src/message/rpc_message_trpc.cc000066400000000000000000001171261525217424400222200ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include #include #include "rpc_message_trpc.h" #include "rpc_meta_trpc.pb.h" #include "rpc_basic.h" #include "rpc_compress.h" #include "rpc_zero_copy_stream.h" #include "rpc_module.h" namespace srpc { namespace TRPCHttpHeaders { const std::string CompressType = "Content-Encoding"; const std::string DataType = "Content-Type"; const std::string CallType = "trpc-call-type"; const std::string RequestId = "trpc-request-id"; const std::string Timeout = "trpc-timeout"; const std::string Caller = "trpc-caller"; const std::string Callee = "trpc-callee"; const std::string Func = "trpc-func"; const std::string Ret = "trpc-ret"; const std::string FuncRet = "trpc-func-ret"; const std::string ErrorMsg = "trpc-error-msg"; const std::string MessageType = "trpc-message-type"; const std::string TransInfo = "trpc-trans-info"; const std::string SRPCStatus = "SRPC-Status"; const std::string SRPCError = "SRPC-Error"; } namespace TRPCHttpHeadersCode { enum { Unknown = 0, CompressType, DataType, CallType, RequestId, Timeout, Caller, Callee, Func, Ret, FuncRet, ErrorMsg, MessageType, TransInfo, SRPCStatus, SRPCError }; } struct CaseCmp { bool operator()(const std::string& lhs, const std::string& rhs) const { return strcasecmp(lhs.c_str(), rhs.c_str()) < 0; } }; static int GetHttpHeadersCode(const std::string& header) { static const std::map M = { {TRPCHttpHeaders::CompressType, TRPCHttpHeadersCode::CompressType}, {TRPCHttpHeaders::DataType, TRPCHttpHeadersCode::DataType}, {TRPCHttpHeaders::CallType, TRPCHttpHeadersCode::CallType}, {TRPCHttpHeaders::RequestId, TRPCHttpHeadersCode::RequestId}, {TRPCHttpHeaders::Timeout, TRPCHttpHeadersCode::Timeout}, {TRPCHttpHeaders::Caller, TRPCHttpHeadersCode::Caller}, {TRPCHttpHeaders::Callee, TRPCHttpHeadersCode::Callee}, {TRPCHttpHeaders::Func, TRPCHttpHeadersCode::Func}, {TRPCHttpHeaders::Ret, TRPCHttpHeadersCode::Ret}, {TRPCHttpHeaders::FuncRet, TRPCHttpHeadersCode::FuncRet}, {TRPCHttpHeaders::ErrorMsg, TRPCHttpHeadersCode::ErrorMsg}, {TRPCHttpHeaders::MessageType, TRPCHttpHeadersCode::MessageType}, {TRPCHttpHeaders::TransInfo, TRPCHttpHeadersCode::TransInfo}, {TRPCHttpHeaders::SRPCStatus, TRPCHttpHeadersCode::SRPCStatus}, {TRPCHttpHeaders::SRPCError, TRPCHttpHeadersCode::SRPCError} }; auto it = M.find(header); return it == M.end() ? TRPCHttpHeadersCode::Unknown : it->second; } static int GetHttpDataType(const std::string &type) { static const std::unordered_map M = { {"application/json", RPCDataJson}, {"application/x-protobuf", RPCDataProtobuf}, {"application/protobuf", RPCDataProtobuf}, {"application/pb", RPCDataProtobuf}, {"application/proto", RPCDataProtobuf} }; auto it = M.find(type); return it == M.end() ? RPCDataUndefined : it->second; } static std::string GetHttpDataTypeStr(int type) { switch (type) { case RPCDataJson: return "application/json"; case RPCDataProtobuf: return "application/proto"; } return ""; } static int GetHttpCompressType(const std::string &type) { static const std::unordered_map M = { {"identity", RPCCompressNone}, {"x-snappy", RPCCompressSnappy}, {"gzip", RPCCompressGzip}, {"deflate", RPCCompressZlib}, {"x-lz4", RPCCompressLz4} }; auto it = M.find(type); return it == M.end() ? RPCCompressNone : it->second; } static std::string GetHttpCompressTypeStr(int type) { switch (type) { case RPCCompressNone: return "identity"; case RPCCompressSnappy: return "x-snappy"; case RPCCompressGzip: return "gzip"; case RPCCompressZlib: return "deflate"; case RPCCompressLz4: return "x-lz4"; } return ""; } static int Base64Encode(const std::string& in, std::string& out) { size_t len = in.length(); size_t base64_len = (len + 2) / 3 * 4; const unsigned char *f = (const unsigned char *)in.c_str(); out.resize(base64_len + 1); EVP_EncodeBlock((unsigned char *)&out[0], f, len); out.pop_back(); return 0; } static int Base64Decode(const char *in, size_t len, std::string& out) { size_t origin_len = (len + 3) / 4 * 3; const unsigned char *f = (const unsigned char *)in; int ret; int zeros = 0; out.resize(origin_len); ret = EVP_DecodeBlock((unsigned char *)&out[0], f, len); if (ret < 0) return ret; while (len != 0 && in[--len] == '=') zeros++; if (zeros > 2) return -1; out.resize(ret - zeros); return 0; } static std::string JsonEscape(const std::string& in) { std::string out; out.reserve(in.size()); for (char c : in) { switch (c) { case '\"': out.append("\\\""); break; case '\\': out.append("\\\\"); break; case '/': out.append("\\/"); break; case '\b': out.append("\\b"); break; case '\f': out.append("\\f"); break; case '\n': out.append("\\n"); break; case '\r': out.append("\\r"); break; case '\t': out.append("\\t"); break; default: out.push_back(c); break; } } return out; } using TransInfoMap = ::google::protobuf::Map<::std::string, ::std::string>; static int DecodeTransInfo(const std::string& content, TransInfoMap& map) { json_value_t *json; json_object_t *obj; const json_value_t *value; const char *name, *str; size_t len; std::string decoded; int errno_bak = errno; errno = EBADMSG; json = json_value_parse(content.c_str()); if (json == nullptr) return -1; errno = errno_bak; if (json_value_type(json) != JSON_VALUE_OBJECT) { json_value_destroy(json); return -1; } obj = json_value_object(json); json_object_for_each(name, value, obj) { str = json_value_string(value); if (!name || !str) continue; len = strlen(str); if (Base64Decode(str, len, decoded) == 0) map[name].assign(std::move(decoded)); else map[name].assign(str, len); } json_value_destroy(json); return 0; } static std::string EncodeTransInfo(const TransInfoMap& map) { std::string s; std::string encoded; s.append("{"); for (const auto& kv : map) { Base64Encode(kv.second, encoded); s.append("\"").append(JsonEscape(kv.first)).append("\":"); s.append("\"").append(encoded).append("\","); } if (s.back() == ',') s.back() = '}'; else s.push_back('}'); return s; } static constexpr const char *kTypePrefix = "type.googleapis.com"; class ResolverInstance { public: static google::protobuf::util::TypeResolver *get_resolver() { static ResolverInstance kInstance; return kInstance.resolver_; } private: ResolverInstance() { resolver_ = google::protobuf::util::NewTypeResolverForDescriptorPool( kTypePrefix, google::protobuf::DescriptorPool::generated_pool()); } ~ResolverInstance() { delete resolver_; } google::protobuf::util::TypeResolver *resolver_; }; static inline std::string GetTypeUrl(const ProtobufIDLMessage *pb_msg) { return kTypePrefix + ("/" + std::string(pb_msg->GetDescriptor()->full_name())); } TRPCMessage::TRPCMessage() { this->nreceived = 0; this->meta_buf = NULL; this->meta_len = 0; this->message_len = 0; memset(this->header, 0, TRPC_HEADER_SIZE); this->message = new RPCBuffer(); } TRPCMessage::~TRPCMessage() { delete this->message; delete []this->meta_buf; delete this->meta; } TRPCRequest::TRPCRequest() { this->meta = new RequestProtocol(); } TRPCResponse::TRPCResponse() { this->meta = new ResponseProtocol(); } int TRPCMessage::encode(struct iovec vectors[], int max, size_t size_limit) { size_t sz = TRPC_HEADER_SIZE + this->meta_len + this->message_len; if (sz > 0x7FFFFFFF) { errno = EOVERFLOW; return -1; } int ret; char *p = this->header; *(uint16_t *)p = ntohs((uint16_t)TrpcMagic::TRPC_MAGIC_VALUE); p += 2; p += 1; // TrpcDataFrameType p += 1; // TrpcDataFrameState *(uint32_t *)(p) = htonl((uint32_t)sz); p += 4; *(uint16_t *)(p) = htons((uint16_t)this->meta_len); // 2: stream_id + 4 : reserved vectors[0].iov_base = this->header; vectors[0].iov_len = TRPC_HEADER_SIZE; vectors[1].iov_base = this->meta_buf; vectors[1].iov_len = this->meta_len; ret = this->message->encode(vectors + 2, max - 2); if (ret < 0) return ret; return 2 + ret; } int TRPCMessage::append(const void *buf, size_t *size, size_t size_limit) { uint32_t *p; uint16_t *sp; size_t header_left, body_received, buf_len; if (this->nreceived < TRPC_HEADER_SIZE) { //receive header header_left = TRPC_HEADER_SIZE - this->nreceived; if (*size >= header_left) { //receive the whole header and ready to recieve body memcpy(this->header + this->nreceived, buf, header_left); this->nreceived += header_left; sp = (uint16_t *)this->header; uint16_t magic_value = ntohs(*sp); if (magic_value != TrpcMagic::TRPC_MAGIC_VALUE || this->header[2] || this->header[3]) { errno = EBADMSG; return -1; } p = (uint32_t *)this->header + 1; buf_len = ntohl(*p); sp = (uint16_t *)this->header + 4; this->meta_len = ntohs(*sp); if (buf_len < TRPC_HEADER_SIZE || this->meta_len > buf_len - TRPC_HEADER_SIZE) { errno = EBADMSG; return -1; } buf_len -= TRPC_HEADER_SIZE; this->message_len = buf_len - this->meta_len; if (buf_len >= size_limit) { errno = EMSGSIZE; return -1; } else if (buf_len > 0) { if (*size - header_left > buf_len) *size = header_left + buf_len; this->meta_buf = new char[this->meta_len]; if (*size - header_left <= this->meta_len) { memcpy(this->meta_buf, (const char *)buf + header_left, *size - header_left); } else { memcpy(this->meta_buf, (const char *)buf + header_left, this->meta_len); this->message->append((const char *)buf + header_left + this->meta_len, *size - header_left - this->meta_len, BUFFER_MODE_COPY); } this->nreceived += *size - header_left; if (this->nreceived == TRPC_HEADER_SIZE + buf_len) return 1; else return 0; } else if (*size == header_left) { return 1; // means body_size == 0 and finish recieved header } else { // means buf_len < 0 errno = EBADMSG; return -1; } } else { // only receive header memcpy(this->header + this->nreceived, buf, *size); this->nreceived += *size; return 0; } } else { // have already received the header and now is for body only body_received = this->nreceived - TRPC_HEADER_SIZE; buf_len = this->meta_len + this->message_len; if (body_received + *size > buf_len) *size = buf_len - body_received; if (body_received + *size <= this->meta_len) { memcpy(this->meta_buf + body_received, buf, *size); } else if (body_received < this->meta_len) { memcpy(this->meta_buf + body_received, buf, this->meta_len - body_received); if (body_received + *size > this->meta_len)// useless. always true this->message->append((const char *)buf + this->meta_len - body_received, *size - this->meta_len + body_received, BUFFER_MODE_COPY); } else { this->message->append((const char *)buf, *size, BUFFER_MODE_COPY); } this->nreceived += *size; return this->nreceived == TRPC_HEADER_SIZE + buf_len; } } bool TRPCRequest::deserialize_meta() { RequestProtocol *meta = static_cast(this->meta); if (!meta->ParseFromArray(this->meta_buf, (int)this->meta_len)) return false; if (meta->version() != TrpcProtoVersion::TRPC_PROTO_V1 || meta->call_type() != TrpcCallType::TRPC_UNARY_CALL || meta->content_type() != TrpcContentEncodeType::TRPC_PROTO_ENCODE) { // this->trpc_error = ST_ERR_UNSUPPORTED_PROTO_TYPE; return false; } // this->timeout = meta->timeout(); return true; } bool TRPCResponse::deserialize_meta() { ResponseProtocol *meta = static_cast(this->meta); if (!meta->ParseFromArray(this->meta_buf, (int)this->meta_len)) return false; this->srpc_status_code = this->status_code_trpc_srpc(meta->ret()); if (!meta->error_msg().empty()) this->srpc_error_msg = meta->error_msg().data(); return true; } bool TRPCRequest::serialize_meta() { RequestProtocol *meta = static_cast(this->meta); meta->set_content_type(TrpcContentEncodeType::TRPC_PROTO_ENCODE); meta->set_version(TrpcProtoVersion::TRPC_PROTO_V1); meta->set_call_type(TrpcCallType::TRPC_UNARY_CALL); this->meta_len = meta->ByteSizeLong(); this->meta_buf = new char[this->meta_len]; return this->meta->SerializeToArray(this->meta_buf, (int)this->meta_len); } bool TRPCResponse::serialize_meta() { ResponseProtocol *meta = static_cast(this->meta); meta->set_version(TrpcProtoVersion::TRPC_PROTO_V1); meta->set_call_type(TrpcCallType::TRPC_UNARY_CALL); meta->set_ret(this->status_code_srpc_trpc(this->srpc_status_code)); // meta->set_error_msg(this->error_msg_srpc_trpc(this->srpc_status_code)); meta->set_error_msg(this->srpc_error_msg); this->meta_len = meta->ByteSizeLong(); this->meta_buf = new char[this->meta_len]; return meta->SerializeToArray(this->meta_buf, (int)this->meta_len); } inline int TRPCMessage::compress_type_trpc_srpc(int trpc_content_encoding) const { switch (trpc_content_encoding) { case TrpcCompressType::TRPC_DEFAULT_COMPRESS : return RPCCompressNone; case TrpcCompressType::TRPC_GZIP_COMPRESS : return RPCCompressGzip; case TrpcCompressType::TRPC_SNAPPY_COMPRESS : return RPCCompressSnappy; default : return -1; } } inline int TRPCMessage::compress_type_srpc_trpc(int srpc_compress_type) const { switch (srpc_compress_type) { case RPCCompressNone : return TrpcCompressType::TRPC_DEFAULT_COMPRESS; case RPCCompressGzip : return TrpcCompressType::TRPC_GZIP_COMPRESS; case RPCCompressSnappy : return TrpcCompressType::TRPC_SNAPPY_COMPRESS; case RPCCompressZlib : return TrpcCompressType::TRPC_ZLIB_COMPRESS; case RPCCompressLz4 : return TrpcCompressType::TRPC_LZ4_COMPRESS; default : return -1; } } inline int TRPCMessage::data_type_trpc_srpc(int trpc_content_type) const { switch (trpc_content_type) { case TrpcContentEncodeType::TRPC_PROTO_ENCODE : return RPCDataProtobuf; case TrpcContentEncodeType::TRPC_JSON_ENCODE : return RPCDataJson; default : return -1; } } inline int TRPCMessage::data_type_srpc_trpc(int srpc_data_type) const { switch (srpc_data_type) { case RPCDataProtobuf : return TrpcContentEncodeType::TRPC_PROTO_ENCODE; case RPCDataJson : return TrpcContentEncodeType::TRPC_JSON_ENCODE; default : return -1; } } inline int TRPCMessage::status_code_srpc_trpc(int srpc_status_code) const { switch (srpc_status_code) { case RPCStatusOK: return TrpcRetCode::TRPC_INVOKE_SUCCESS; case RPCStatusServiceNotFound: return TrpcRetCode::TRPC_SERVER_NOSERVICE_ERR; case RPCStatusMethodNotFound: return TrpcRetCode::TRPC_SERVER_NOFUNC_ERR; case RPCStatusURIInvalid: case RPCStatusMetaError: case RPCStatusReqCompressSizeInvalid: case RPCStatusReqCompressNotSupported: case RPCStatusReqCompressError: case RPCStatusReqSerializeError: return TrpcRetCode::TRPC_CLIENT_ENCODE_ERR; case RPCStatusReqDecompressSizeInvalid: case RPCStatusReqDecompressNotSupported: case RPCStatusReqDecompressError: case RPCStatusReqDeserializeError: return TrpcRetCode::TRPC_SERVER_DECODE_ERR; case RPCStatusRespCompressSizeInvalid: case RPCStatusRespCompressNotSupported: case RPCStatusRespCompressError: case RPCStatusRespSerializeError: return TrpcRetCode::TRPC_SERVER_ENCODE_ERR; case RPCStatusRespDecompressSizeInvalid: case RPCStatusRespDecompressNotSupported: case RPCStatusRespDecompressError: case RPCStatusRespDeserializeError: return TrpcRetCode::TRPC_CLIENT_DECODE_ERR; case RPCStatusUpstreamFailed: case RPCStatusDNSError: return TrpcRetCode::TRPC_CLIENT_ROUTER_ERR; case RPCStatusSystemError: return TrpcRetCode::TRPC_SERVER_SYSTEM_ERR; // return TrpcRetCode::TRPC_CLINET_NETWORK_ERR; default: return TrpcRetCode::TRPC_INVOKE_UNKNOWN_ERR; } } inline int TRPCMessage::status_code_trpc_srpc(int trpc_ret_code) const { switch (trpc_ret_code) { case TrpcRetCode::TRPC_INVOKE_SUCCESS: return RPCStatusOK; case TrpcRetCode::TRPC_SERVER_NOSERVICE_ERR: return RPCStatusServiceNotFound; case TrpcRetCode::TRPC_SERVER_NOFUNC_ERR: return RPCStatusMethodNotFound; case TrpcRetCode::TRPC_CLIENT_ENCODE_ERR: return RPCStatusReqSerializeError; case TrpcRetCode::TRPC_SERVER_DECODE_ERR: return RPCStatusReqDeserializeError; case TrpcRetCode::TRPC_SERVER_ENCODE_ERR: return RPCStatusRespSerializeError; case TrpcRetCode::TRPC_CLIENT_DECODE_ERR: return RPCStatusRespDeserializeError; case TrpcRetCode::TRPC_CLIENT_ROUTER_ERR: return RPCStatusUpstreamFailed; // return RPCStatusDNSError; default: return RPCStatusSystemError; } } const char *TRPCMessage::error_msg_srpc_trpc(int srpc_status_code) const { return "";//TODO } int TRPCRequest::get_compress_type() const { RequestProtocol *meta = static_cast(this->meta); return this->compress_type_trpc_srpc(meta->content_encoding()); } void TRPCRequest::set_compress_type(int type) { RequestProtocol *meta = static_cast(this->meta); meta->set_content_encoding(this->compress_type_srpc_trpc(type)); } int TRPCRequest::get_data_type() const { RequestProtocol *meta = static_cast(this->meta); return this->data_type_trpc_srpc(meta->content_type()); } void TRPCRequest::set_data_type(int type) { RequestProtocol *meta = static_cast(this->meta); meta->set_content_type(this->data_type_srpc_trpc(type)); } void TRPCRequest::set_request_id(int32_t req_id) { RequestProtocol *meta = static_cast(this->meta); meta->set_request_id(req_id); } int32_t TRPCRequest::get_request_id() const { RequestProtocol *meta = static_cast(this->meta); return meta->request_id(); } const std::string& TRPCRequest::get_service_name() const { RequestProtocol *meta = static_cast(this->meta); return meta->callee(); } const std::string& TRPCRequest::get_method_name() const { RequestProtocol *meta = static_cast(this->meta); return meta->func(); } // like p.service_name void TRPCRequest::set_service_name(const std::string& service_name) { RequestProtocol *meta = static_cast(this->meta); meta->set_callee(service_name); // useless in transfer protocol } // like /p.service_name/method_name void TRPCRequest::set_method_name(const std::string& method_name) { RequestProtocol *meta = static_cast(this->meta); meta->set_func(method_name); // use this prefix as service_name for route } void TRPCRequest::set_callee_timeout(int timeout) { RequestProtocol *meta = static_cast(this->meta); meta->set_timeout(timeout); } void TRPCRequest::set_caller_name(const std::string& caller_name) { RequestProtocol *meta = static_cast(this->meta); meta->set_caller(caller_name); } const std::string& TRPCRequest::get_caller_name() const { RequestProtocol *meta = static_cast(this->meta); return meta->caller(); } int TRPCResponse::get_compress_type() const { ResponseProtocol *meta = static_cast(this->meta); return this->compress_type_trpc_srpc(meta->content_encoding()); } int TRPCResponse::get_data_type() const { ResponseProtocol *meta = static_cast(this->meta); return this->data_type_trpc_srpc(meta->content_type()); } void TRPCResponse::set_compress_type(int type) { ResponseProtocol *meta = static_cast(this->meta); meta->set_content_encoding(this->compress_type_srpc_trpc(type)); } void TRPCResponse::set_data_type(int type) { ResponseProtocol *meta = static_cast(this->meta); meta->set_content_type(this->data_type_srpc_trpc(type)); } void TRPCResponse::set_request_id(int32_t req_id) { ResponseProtocol *meta = static_cast(this->meta); meta->set_request_id(req_id); } int32_t TRPCResponse::get_request_id() const { ResponseProtocol *meta = static_cast(this->meta); return meta->request_id(); } int TRPCResponse::get_status_code() const { return this->srpc_status_code; } void TRPCResponse::set_status_code(int code) { this->srpc_status_code = code; if (code != RPCStatusOK) this->srpc_error_msg = this->get_errmsg(); } int TRPCResponse::get_error() const { ResponseProtocol *meta = static_cast(this->meta); return meta->ret();//TODO } void TRPCResponse::set_error(int error) { ResponseProtocol *meta = static_cast(this->meta); meta->set_ret(error); } const char *TRPCResponse::get_errmsg() const { ResponseProtocol *meta = static_cast(this->meta); return meta->error_msg().c_str(); } int TRPCMessage::serialize(const ProtobufIDLMessage *pb_msg) { if (!pb_msg) //TODO: make sure trpc is OK to send a NULL user pb_msg return RPCStatusOK; using namespace google::protobuf; ResponseProtocol *meta = dynamic_cast(this->meta); bool is_resp = (meta != NULL); int data_type = this->get_data_type(); RPCOutputStream output_stream(this->message, pb_msg->ByteSizeLong()); int ret; if (data_type == RPCDataProtobuf) ret = pb_msg->SerializeToZeroCopyStream(&output_stream) ? 0 : -1; else if (data_type == RPCDataJson) { std::string binary_input = pb_msg->SerializeAsString(); io::ArrayInputStream input_stream(binary_input.data(), (int)binary_input.size()); const auto *pool = pb_msg->GetDescriptor()->file()->pool(); auto *resolver = (pool == DescriptorPool::generated_pool() ? ResolverInstance::get_resolver() : util::NewTypeResolverForDescriptorPool(kTypePrefix, pool)); util::JsonPrintOptions options; options.add_whitespace = this->get_json_add_whitespace(); options.always_print_enums_as_ints = this->get_json_enums_as_ints(); options.preserve_proto_field_names = this->get_json_preserve_names(); #if GOOGLE_PROTOBUF_VERSION >= 5026000 options.always_print_fields_with_no_presence = this->get_json_fields_no_presence(); #else options.always_print_primitive_fields = this->get_json_fields_no_presence(); #endif ret = BinaryToJsonStream(resolver, GetTypeUrl(pb_msg), &input_stream, &output_stream, options).ok() ? 0 : -1; if (pool != DescriptorPool::generated_pool()) delete resolver; } else ret = -1; this->message_len = this->message->size(); if (ret < 0) return is_resp ? RPCStatusRespSerializeError : RPCStatusReqSerializeError; return RPCStatusOK; } int TRPCMessage::deserialize(ProtobufIDLMessage *pb_msg) { using namespace google::protobuf; ResponseProtocol *meta = dynamic_cast(this->meta); bool is_resp = (meta != NULL); int data_type = this->get_data_type(); int ret; RPCInputStream input_stream(this->message); if (data_type == RPCDataProtobuf) ret = pb_msg->ParseFromZeroCopyStream(&input_stream) ? 0 : -1; else if (data_type == RPCDataJson) { std::string binary_output; io::StringOutputStream output_stream(&binary_output); const auto *pool = pb_msg->GetDescriptor()->file()->pool(); auto *resolver = (pool == DescriptorPool::generated_pool() ? ResolverInstance::get_resolver() : util::NewTypeResolverForDescriptorPool(kTypePrefix, pool)); util::JsonParseOptions options; options.ignore_unknown_fields = true; if (JsonToBinaryStream(resolver, GetTypeUrl(pb_msg), &input_stream, &output_stream, options).ok()) { ret = pb_msg->ParseFromString(binary_output) ? 0 : -1; } else ret = -1; if (pool != DescriptorPool::generated_pool()) delete resolver; } else ret = -1; if (ret < 0) return is_resp ? RPCStatusRespDeserializeError : RPCStatusReqDeserializeError; return RPCStatusOK; } int TRPCMessage::compress() { ResponseProtocol *meta = dynamic_cast(this->meta); bool is_resp = (meta != NULL); int type = this->get_compress_type(); size_t buflen = this->message_len; int status_code = RPCStatusOK; if (buflen == 0) return status_code; if (type == RPCCompressNone) return status_code; static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->lease_compressed_size(type, buflen); if (ret == -2) return is_resp ? RPCStatusReqCompressNotSupported : RPCStatusRespCompressNotSupported; else if (ret <= 0) return is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; //buflen = ret; RPCBuffer *dst_buf = new RPCBuffer(); ret = compressor->serialize_to_compressed(this->message, dst_buf, type); if (ret == -2) { status_code = is_resp ? RPCStatusRespCompressNotSupported : RPCStatusReqCompressNotSupported; } else if (ret == -1) { status_code = is_resp ? RPCStatusRespCompressError : RPCStatusReqCompressError; } else if (ret <= 0) { status_code = is_resp ? RPCStatusRespCompressSizeInvalid : RPCStatusReqCompressSizeInvalid; } else buflen = ret; if (status_code == RPCStatusOK) { delete this->message; this->message = dst_buf; this->message_len = buflen; } else delete dst_buf; return status_code; } int TRPCMessage::decompress() { ResponseProtocol *meta = dynamic_cast(this->meta); bool is_resp = (meta != NULL); int type = this->get_compress_type(); int status_code = RPCStatusOK; if (this->message_len == 0 || type == RPCCompressNone) return status_code; RPCBuffer *dst_buf = new RPCBuffer(); static RPCCompressor *compressor = RPCCompressor::get_instance(); int ret = compressor->parse_from_compressed(this->message, dst_buf, type); if (ret == -2) { status_code = is_resp ? RPCStatusRespDecompressNotSupported : RPCStatusReqDecompressNotSupported; } else if (ret == -1) { status_code = is_resp ? RPCStatusRespDecompressError : RPCStatusReqDecompressError; } else if (ret <= 0) { status_code = is_resp ? RPCStatusRespDecompressSizeInvalid : RPCStatusReqDecompressSizeInvalid; } if (status_code == RPCStatusOK) { delete this->message; this->message = dst_buf; this->message_len = ret; } else delete dst_buf; return status_code; } static std::string set_trace_parent(std::string& trace, std::string& span, const RPCModuleData& data) { std::string str = "00-"; // set version char trace_id_buf[SRPC_TRACEID_SIZE * 2 + 1]; TRACE_ID_BIN_TO_HEX((uint64_t *)trace.data(), trace_id_buf); str.append(trace_id_buf); str.append("-"); char span_id_buf[SRPC_SPANID_SIZE * 2 + 1]; SPAN_ID_BIN_TO_HEX((uint64_t *)span.data(), span_id_buf); str.append(span_id_buf); str.append("-"); str.append("01"); // set traceflag : sampled return str; } bool TRPCRequest::set_meta_module_data(const RPCModuleData& data) { RequestProtocol *meta = static_cast(this->meta); std::string trace; std::string span; int flag = 0; for (const auto & pair : data) { if (pair.first.compare(SRPC_TRACE_ID) == 0) { trace = pair.second; flag |= 1; } else if (pair.first.compare(SRPC_SPAN_ID) == 0) { span = pair.second; flag |= (1 << 1); } else meta->mutable_trans_info()->insert({pair.first, pair.second}); } if (flag == 3) meta->mutable_trans_info()->insert({OTLP_TRACE_PARENT, set_trace_parent(trace, span, data)}); return true; } static bool get_trace_parent(const std::string& str, RPCModuleData& data) { // only support version = 00 : "00-" trace-id "-" parent-id "-" trace-flags size_t begin = OTLP_TRACE_VERSION_SIZE; if (str.length() < 55 || str.substr(0, begin).compare("00") != 0) return false; // data[OTLP_TRACE_VERSION] = "00"; uint64_t trace[2]; begin += 1; TRACE_ID_HEX_TO_BIN(str.substr(begin, SRPC_TRACEID_SIZE * 2).data(), trace); data[SRPC_TRACE_ID] = std::string((char *)trace, SRPC_TRACEID_SIZE); uint64_t span[1]; begin += SRPC_TRACEID_SIZE * 2 + 1; SPAN_ID_HEX_TO_BIN(str.substr(begin, SRPC_SPANID_SIZE * 2).data(), span); data[SRPC_SPAN_ID] = std::string((char *)span, SRPC_SPANID_SIZE); // begin += SRPC_SPANID_SIZE + 1; // data[OTLP_TRACE_FLAG] = str.substr(begin); return true; } bool TRPCRequest::get_meta_module_data(RPCModuleData& data) const { RequestProtocol *meta = static_cast(this->meta); for (const auto & pair : meta->trans_info()) { if (pair.first.compare(OTLP_TRACE_PARENT) == 0) get_trace_parent(pair.second, data); else if (pair.first.compare(OTLP_TRACE_STATE) == 0) ;// TODO: support tracestate else data.insert(pair); } return true; } bool TRPCRequest::trim_method_prefix() { RequestProtocol *meta = static_cast(this->meta); std::string *func = meta->mutable_func(); if ((*func)[0] == '/') { size_t pos = func->find('/', 1); if (pos != std::string::npos) { // callee is not guaranteed being set as service_name in transfer protocol // so server need to get from func and set on callee for local use meta->set_callee(func->substr(1, pos - 1)); meta->set_func(func->substr(pos + 1, func->length())); return true; } } meta->set_callee(""); // make it fail in find_service() return false; } bool TRPCHttpRequest::trim_method_prefix() { RequestProtocol *meta = static_cast(this->meta); std::string *method = meta->mutable_func(); auto pos = method->find_last_of('/'); if (pos == std::string::npos) return false; meta->set_func(method->substr(pos + 1, method->length())); return true; } bool TRPCResponse::set_meta_module_data(const RPCModuleData& data) { ResponseProtocol *meta = static_cast(this->meta); for (const auto & pair : data) meta->mutable_trans_info()->insert({pair.first, pair.second}); return true; } bool TRPCResponse::get_meta_module_data(RPCModuleData& data) const { ResponseProtocol *meta = static_cast(this->meta); for (const auto & pair : meta->trans_info()) data.insert(pair); return true; } bool TRPCHttpRequest::serialize_meta() { if (this->message->size() > 0x7FFFFFFF) return false; int data_type = this->get_data_type(); int compress_type = this->get_compress_type(); std::string uri("/"); uri += this->get_service_name(); uri += "/"; uri += this->get_method_name(); set_http_version("HTTP/1.1"); set_method("POST"); set_request_uri(uri); //set header set_header_pair(TRPCHttpHeaders::DataType, GetHttpDataTypeStr(data_type)); set_header_pair(TRPCHttpHeaders::CompressType, GetHttpCompressTypeStr(compress_type)); set_header_pair("Connection", "Keep-Alive"); set_header_pair("Content-Length", std::to_string(this->message_len)); set_header_pair(TRPCHttpHeaders::CallType, "0"); set_header_pair(TRPCHttpHeaders::RequestId, std::to_string(this->get_request_id())); set_header_pair(TRPCHttpHeaders::Callee, this->get_method_name()); set_header_pair(TRPCHttpHeaders::Func, this->get_service_name()); set_header_pair(TRPCHttpHeaders::Caller, this->get_caller_name()); auto *req_meta = (RequestProtocol *)this->meta; set_header_pair(TRPCHttpHeaders::TransInfo, EncodeTransInfo(req_meta->trans_info())); const void *buffer; size_t buflen; while (buflen = this->message->fetch(&buffer), buffer && buflen > 0) this->append_output_body_nocopy(buffer, buflen); return true; } bool TRPCHttpRequest::deserialize_meta() { const char *request_uri = this->get_request_uri(); protocol::HttpHeaderCursor header_cursor(this); auto *meta = (RequestProtocol *)this->meta; std::string key, value; this->set_data_type(RPCDataJson); this->set_compress_type(RPCCompressNone); while (header_cursor.next(key, value)) { switch (GetHttpHeadersCode(key)) { case TRPCHttpHeadersCode::DataType: this->set_data_type(GetHttpDataType(value)); break; case TRPCHttpHeadersCode::CompressType: this->set_compress_type(GetHttpCompressType(value)); break; case TRPCHttpHeadersCode::CallType: meta->set_call_type(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::RequestId: meta->set_request_id(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::Timeout: meta->set_timeout(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::Caller: meta->set_caller(value); break; case TRPCHttpHeadersCode::Callee: meta->set_callee(value); break; case TRPCHttpHeadersCode::MessageType: meta->set_message_type(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::TransInfo: DecodeTransInfo(value, *meta->mutable_trans_info()); break; default: break; } } if (request_uri && request_uri[0] == '/') { std::string str = request_uri + 1; auto pos = str.find_first_of("?#"); if (pos != std::string::npos) str.erase(pos); if (!str.empty() && str.back() == '/') str.pop_back(); pos = str.find_last_of('/'); if (pos != std::string::npos) { this->set_service_name(str.substr(0, pos)); this->set_method_name(str.substr(pos + 1)); } } const void *ptr; size_t len; this->get_parsed_body(&ptr, &len); if (len > 0x7FFFFFFF) return false; protocol::HttpChunkCursor chunk_cursor(this); RPCBuffer *buf = this->get_buffer(); size_t msg_len = 0; while (chunk_cursor.next(&ptr, &len)) { msg_len += len; buf->append((const char *)ptr, len, BUFFER_MODE_NOCOPY); } if (this->get_compress_type() == RPCCompressNone && msg_len == 0 && this->get_data_type() == RPCDataJson) { buf->append("{}", 2, BUFFER_MODE_NOCOPY); msg_len = 2; } this->message_len = msg_len; return true; } bool TRPCHttpResponse::serialize_meta() { if (this->message->size() > 0x7FFFFFFF) return false; auto *meta = (ResponseProtocol *)this->meta; int data_type = this->get_data_type(); int compress_type = this->get_compress_type(); int rpc_status_code = this->get_status_code(); int rpc_error = this->get_error(); const char *http_status_code = this->protocol::HttpResponse::get_status_code(); set_http_version("HTTP/1.1"); if (rpc_status_code == RPCStatusOK) { if (http_status_code) protocol::HttpUtil::set_response_status(this, atoi(http_status_code)); else protocol::HttpUtil::set_response_status(this, HttpStatusOK); } else if (rpc_status_code == RPCStatusServiceNotFound || rpc_status_code == RPCStatusMethodNotFound || rpc_status_code == RPCStatusMetaError || rpc_status_code == RPCStatusURIInvalid) { protocol::HttpUtil::set_response_status(this, HttpStatusBadRequest); } else if (rpc_status_code == RPCStatusRespCompressNotSupported || rpc_status_code == RPCStatusRespDecompressNotSupported || rpc_status_code == RPCStatusIDLSerializeNotSupported || rpc_status_code == RPCStatusIDLDeserializeNotSupported) { protocol::HttpUtil::set_response_status(this, HttpStatusNotImplemented); } else if (rpc_status_code == RPCStatusUpstreamFailed) { protocol::HttpUtil::set_response_status(this, HttpStatusServiceUnavailable); } else { protocol::HttpUtil::set_response_status(this, HttpStatusInternalServerError); } //set header set_header_pair(TRPCHttpHeaders::SRPCStatus, std::to_string(rpc_status_code)); set_header_pair(TRPCHttpHeaders::SRPCError, std::to_string(rpc_error)); set_header_pair(TRPCHttpHeaders::DataType, GetHttpDataTypeStr(data_type)); set_header_pair(TRPCHttpHeaders::CompressType, GetHttpCompressTypeStr(compress_type)); set_header_pair(TRPCHttpHeaders::CallType, std::to_string(meta->call_type())); set_header_pair(TRPCHttpHeaders::RequestId, std::to_string(meta->request_id())); set_header_pair(TRPCHttpHeaders::Ret, std::to_string(meta->ret())); set_header_pair(TRPCHttpHeaders::FuncRet, std::to_string(meta->func_ret())); set_header_pair(TRPCHttpHeaders::ErrorMsg, meta->error_msg()); set_header_pair(TRPCHttpHeaders::MessageType, std::to_string(meta->message_type())); set_header_pair("Content-Length", std::to_string(this->message_len)); set_header_pair("Connection", "Keep-Alive"); set_header_pair(TRPCHttpHeaders::TransInfo, EncodeTransInfo(meta->trans_info())); const void *buffer; size_t buflen; while (buflen = this->message->fetch(&buffer), buffer && buflen > 0) this->append_output_body_nocopy(buffer, buflen); return true; } bool TRPCHttpResponse::deserialize_meta() { protocol::HttpHeaderCursor header_cursor(this); auto *meta = (ResponseProtocol *)this->meta; std::string key, value; this->set_data_type(RPCDataJson); this->set_compress_type(RPCCompressNone); while (header_cursor.next(key, value)) { switch (GetHttpHeadersCode(key)) { case TRPCHttpHeadersCode::DataType: this->set_data_type(GetHttpDataType(value)); break; case TRPCHttpHeadersCode::CompressType: this->set_compress_type(GetHttpCompressType(value)); break; case TRPCHttpHeadersCode::CallType: meta->set_call_type(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::RequestId: meta->set_request_id(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::Ret: meta->set_ret(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::FuncRet: meta->set_func_ret(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::ErrorMsg: meta->set_error_msg(value); break; case TRPCHttpHeadersCode::MessageType: meta->set_message_type(std::atoi(value.c_str())); break; case TRPCHttpHeadersCode::TransInfo: DecodeTransInfo(value, *meta->mutable_trans_info()); break; default: break; } } const void *ptr; size_t len; this->get_parsed_body(&ptr, &len); if (len > 0x7FFFFFFF) return false; protocol::HttpChunkCursor chunk_cursor(this); RPCBuffer *buf = this->get_buffer(); size_t msg_len = 0; while (chunk_cursor.next(&ptr, &len)) { buf->append((const char *)ptr, len, BUFFER_MODE_NOCOPY); msg_len += len; } this->message_len = msg_len; return true; } bool TRPCHttpRequest::set_meta_module_data(const RPCModuleData& data) { return this->TRPCRequest::set_meta_module_data(data); } bool TRPCHttpRequest::get_meta_module_data(RPCModuleData& data) const { return this->TRPCRequest::get_meta_module_data(data); } bool TRPCHttpResponse::set_meta_module_data(const RPCModuleData& data) { return this->TRPCResponse::set_meta_module_data(data); } bool TRPCHttpResponse::get_meta_module_data(RPCModuleData& data) const { return this->TRPCResponse::get_meta_module_data(data); } bool TRPCHttpRequest::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool TRPCHttpRequest::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool TRPCHttpRequest::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } bool TRPCHttpResponse::set_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::set_header_pair(name, value); } bool TRPCHttpResponse::add_http_header(const std::string& name, const std::string& value) { return this->protocol::HttpMessage::add_header_pair(name, value); } bool TRPCHttpResponse::get_http_header(const std::string& name, std::string& value) const { protocol::HttpHeaderCursor cursor(this); return cursor.find(name, value); } } // namespace srpc sogou-srpc-ac65014/src/message/rpc_message_trpc.h000066400000000000000000000217511525217424400220600ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #ifndef __RPC_MESSAGE_TRPC_H__ #define __RPC_MESSAGE_TRPC_H__ #ifdef _WIN32 #include #else #include #endif #include #include "rpc_message.h" #include "rpc_basic.h" namespace srpc { static constexpr int TRPC_HEADER_SIZE = 16; class TRPCMessage : public RPCMessage { public: TRPCMessage(); virtual ~TRPCMessage(); int encode(struct iovec vectors[], int max, size_t size_limit); int append(const void *buf, size_t *size, size_t size_limit); void set_attachment_nocopy(const char *attachment, size_t len) { } bool get_attachment_nocopy(const char **attachment, size_t *len) const { return false; } public: using RPCMessage::serialize; using RPCMessage::deserialize; int serialize(const ProtobufIDLMessage *pb_msg) override; int deserialize(ProtobufIDLMessage *pb_msg) override; int compress() override; int decompress() override; protected: char header[TRPC_HEADER_SIZE]; size_t nreceived; size_t meta_len; size_t message_len; char *meta_buf; RPCBuffer *message; ProtobufIDLMessage *meta; protected: int compress_type_trpc_srpc(int trpc_content_encoding) const; int compress_type_srpc_trpc(int srpc_compress_type) const; int data_type_trpc_srpc(int trpc_content_type) const; int data_type_srpc_trpc(int srpc_data_type) const; int status_code_srpc_trpc(int srpc_status_code) const; int status_code_trpc_srpc(int trpc_ret_code) const; const char *error_msg_srpc_trpc(int srpc_status_code) const; RPCBuffer *get_buffer() const { return this->message; } }; class TRPCRequest : public TRPCMessage { public: TRPCRequest(); bool serialize_meta(); bool deserialize_meta(); const std::string& get_service_name() const; const std::string& get_method_name() const; const std::string& get_caller_name() const; void set_service_name(const std::string& service_name); void set_method_name(const std::string& method_name); void set_callee_timeout(int timeout); void set_caller_name(const std::string& caller_name); int get_compress_type() const override; void set_compress_type(int type) override; int get_data_type() const override; void set_data_type(int type) override; void set_request_id(int32_t req_id); int32_t get_request_id() const; bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; bool trim_method_prefix(); }; class TRPCResponse : public TRPCMessage { public: TRPCResponse(); bool serialize_meta(); bool deserialize_meta(); int get_compress_type() const override; void set_compress_type(int type) override; int get_data_type() const override; void set_data_type(int type) override; int get_status_code() const; int get_error() const; const char *get_errmsg() const; void set_status_code(int code); void set_error(int error); void set_request_id(int32_t req_id); int32_t get_request_id() const; bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; protected: int srpc_status_code = RPCStatusOK; std::string srpc_error_msg; }; class TRPCStdRequest : public protocol::ProtocolMessage, public RPCRequest, public TRPCRequest { public: int encode(struct iovec vectors[], int max) override { return this->TRPCRequest::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->TRPCRequest::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->TRPCRequest::serialize_meta(); } bool deserialize_meta() override { return this->TRPCRequest::deserialize_meta(); } public: const std::string& get_service_name() const override { return this->TRPCRequest::get_service_name(); } const std::string& get_method_name() const override { return this->TRPCRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->TRPCRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->TRPCRequest::set_method_name(method_name); } bool set_meta_module_data(const RPCModuleData& data) override { return this->TRPCRequest::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->TRPCRequest::get_meta_module_data(data); } void set_caller_name(const std::string& caller_name) { return this->TRPCRequest::set_caller_name(caller_name); } public: TRPCStdRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class TRPCStdResponse : public protocol::ProtocolMessage, public RPCResponse, public TRPCResponse { public: int encode(struct iovec vectors[], int max) override { return this->TRPCResponse::encode(vectors, max, this->size_limit); } int append(const void *buf, size_t *size) override { return this->TRPCResponse::append(buf, size, this->size_limit); } public: bool serialize_meta() override { return this->TRPCResponse::serialize_meta(); } bool deserialize_meta() override { return this->TRPCResponse::deserialize_meta(); } public: int get_status_code() const override { return this->TRPCResponse::get_status_code(); } int get_error() const override { return this->TRPCResponse::get_error(); } const char *get_errmsg() const override { return this->TRPCResponse::get_errmsg(); } void set_status_code(int code) override { return this->TRPCResponse::set_status_code(code); } void set_error(int error) override { return this->TRPCResponse::set_error(error); } bool set_meta_module_data(const RPCModuleData& data) override { return this->TRPCResponse::set_meta_module_data(data); } bool get_meta_module_data(RPCModuleData& data) const override { return this->TRPCResponse::get_meta_module_data(data); } public: TRPCStdResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class TRPCHttpRequest : public protocol::HttpRequest, public RPCRequest, public TRPCRequest { public: bool serialize_meta() override; bool deserialize_meta() override; public: const std::string& get_service_name() const override { return this->TRPCRequest::get_service_name(); } const std::string& get_method_name() const override { return this->TRPCRequest::get_method_name(); } void set_service_name(const std::string& service_name) override { return this->TRPCRequest::set_service_name(service_name); } void set_method_name(const std::string& method_name) override { return this->TRPCRequest::set_method_name(method_name); } bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; void set_caller_name(const std::string& caller_name) { return this->TRPCRequest::set_caller_name(caller_name); } const std::string& get_caller_name() const { return this->TRPCRequest::get_caller_name(); } bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; bool trim_method_prefix(); public: TRPCHttpRequest() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; class TRPCHttpResponse : public protocol::HttpResponse, public RPCResponse, public TRPCResponse { public: bool serialize_meta() override; bool deserialize_meta() override; public: int get_status_code() const override { return this->TRPCResponse::get_status_code(); } int get_error() const override { return this->TRPCResponse::get_error(); } const char *get_errmsg() const override { return this->TRPCResponse::get_errmsg(); } void set_status_code(int code) override { return this->TRPCResponse::set_status_code(code); } void set_error(int error) override { return this->TRPCResponse::set_error(error); } bool set_http_code(int code) override { return this->protocol::HttpResponse::set_status_code(std::to_string(code)); } bool set_meta_module_data(const RPCModuleData& data) override; bool get_meta_module_data(RPCModuleData& data) const override; bool set_http_header(const std::string& name, const std::string& value) override; bool add_http_header(const std::string& name, const std::string& value) override; bool get_http_header(const std::string& name, std::string& value) const override; public: TRPCHttpResponse() { this->size_limit = RPC_BODY_SIZE_LIMIT; } }; } // namespace srpc #endif sogou-srpc-ac65014/src/message/rpc_meta.proto000066400000000000000000000013451525217424400212430ustar00rootroot00000000000000syntax="proto2"; message RPCMetaKeyValue { required string key = 1; oneof value { int32 int_value = 2; string str_value = 3; bytes bytes_value = 4; } }; message RPCRequestMeta { optional string service_name = 1; optional string method_name = 2; optional int64 log_id = 3; }; message RPCResponseMeta { optional int32 status_code = 1 [default = 0]; optional int32 error = 2 [default = 0]; }; message RPCMeta { optional RPCRequestMeta request = 1; optional RPCResponseMeta response = 2; optional uint32 srpc_version = 3; optional int32 compress_type = 4 [default = 0]; optional int32 origin_size = 5; optional int32 compressed_size = 6; optional int32 data_type = 7; repeated RPCMetaKeyValue trans_info = 8; }; sogou-srpc-ac65014/src/message/rpc_meta_brpc.proto000066400000000000000000000103221525217424400222440ustar00rootroot00000000000000// Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you 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. syntax="proto2"; // Not support streaming yet //import "brpc/streaming_rpc_meta.proto"; //package brpc; message BrpcMeta { optional BrpcRequestMeta request = 1; optional BrpcResponseMeta response = 2; optional int32 compress_type = 3; optional int64 correlation_id = 4; optional int32 attachment_size = 5; optional ChunkInfo chunk_info = 6; optional bytes authentication_data = 7; optional StreamSettings stream_settings = 8; } message BrpcRequestMeta { required string service_name = 1; required string method_name = 2; optional int64 log_id = 3; optional int64 trace_id = 4; optional int64 span_id = 5; optional int64 parent_span_id = 6; } message BrpcResponseMeta { optional int32 error_code = 1; optional string error_text = 2; } ////// brpc/options.proto ///////// //import "google/protobuf/descriptor.proto"; enum TalkType { TALK_TYPE_NORMAL = 0; TALK_TYPE_ONEWAY = 1; // TODO } enum ProtocolType { PROTOCOL_UNKNOWN = 0; PROTOCOL_BAIDU_STD = 1; /* PROTOCOL_STREAMING_RPC = 2; PROTOCOL_HULU_PBRPC = 3; PROTOCOL_SOFA_PBRPC = 4; PROTOCOL_RTMP = 5; PROTOCOL_THRIFT = 6; PROTOCOL_HTTP = 7; PROTOCOL_PUBLIC_PBRPC = 8; PROTOCOL_NOVA_PBRPC = 9; PROTOCOL_REDIS = 10; PROTOCOL_NSHEAD_CLIENT = 11; // implemented in baidu-rpc-ub PROTOCOL_NSHEAD = 12; PROTOCOL_HADOOP_RPC = 13; PROTOCOL_HADOOP_SERVER_RPC = 14; PROTOCOL_MONGO = 15; // server side only PROTOCOL_UBRPC_COMPACK = 16; PROTOCOL_DIDX_CLIENT = 17; // Client side only PROTOCOL_MEMCACHE = 18; // Client side only PROTOCOL_ITP = 19; PROTOCOL_NSHEAD_MCPACK = 20; PROTOCOL_DISP_IDL = 21; // Client side only PROTOCOL_ERSDA_CLIENT = 22; // Client side only PROTOCOL_UBRPC_MCPACK2 = 23; // Client side only // Reserve special protocol for cds-agent, which depends on FIFO right now PROTOCOL_CDS_AGENT = 24; // Client side only PROTOCOL_ESP = 25; // Client side only PROTOCOL_H2 = 26; */ } message ChunkInfo { required int64 stream_id = 1; required int64 chunk_id = 2; } // for streaming message StreamSettings { required int64 stream_id = 1; optional bool need_feedback = 2 [default = false]; optional bool writable = 3 [default = false]; } enum FrameType { FRAME_TYPE_UNKNOWN = 0; FRAME_TYPE_RST = 1; FRAME_TYPE_CLOSE = 2; FRAME_TYPE_DATA = 3; FRAME_TYPE_FEEDBACK= 4; } message StreamFrameMeta { required int64 stream_id = 1; optional int64 source_stream_id = 2; optional FrameType frame_type = 3; optional bool has_continuation = 4; optional Feedback feedback = 5; } message Feedback { optional int64 consumed_size = 1; } /* extend google.protobuf.ServiceOptions { // Timeout in milliseconds, at service level. optional int64 service_timeout = 90000 [default = 10000]; } extend google.protobuf.MethodOptions { // Talk type. optional TalkType request_talk_type = 90001 [default = TALK_TYPE_NORMAL]; optional TalkType response_talk_type = 90002 [default = TALK_TYPE_NORMAL]; // If set, override service_timeout. optional int64 method_timeout = 90003; // Compression for request/response. optional CompressType request_compression = 90004 [default = COMPRESS_TYPE_NONE]; optional CompressType response_compression = 90005 [default = COMPRESS_TYPE_NONE]; } */ sogou-srpc-ac65014/src/message/rpc_meta_trpc.proto000066400000000000000000000050241525217424400222710ustar00rootroot00000000000000syntax = "proto3"; enum TrpcMagic { TRPC_DEFAULT_NONE = 0x00; TRPC_MAGIC_VALUE = 0x930; } enum TrpcDataFrameType { TRPC_UNARY_FRAME = 0x00; TRPC_STREAM_FRAME = 0x01; } enum TrpcDataFrameState { TRPC_NO_STATE = 0x00; TRPC_STREAM_FINISH = 0x01; } enum TrpcProtoVersion { TRPC_PROTO_V1 = 0; } enum TrpcCallType { TRPC_UNARY_CALL = 0; TRPC_ONEWAY_CALL = 1; TRPC_CLIENT_STREAM_CALL = 2; TRPC_SERVER_STREAM_CALL = 3; TRPC_BIDI_STREAM_CALL = 4; } enum TrpcMessageType { TRPC_DEFAULT = 0x00; TRPC_DYEING_MESSAGE = 0x01; TRPC_TRACE_MESSAGE = 0x02; TRPC_MULTI_ENV_MESSAGE = 0x04; TRPC_GRID_MESSAGE = 0x08; TRPC_SETNAME_MESSAGE = 0x10; } enum TrpcContentEncodeType { TRPC_PROTO_ENCODE = 0; TRPC_JCE_ENCODE = 1; TRPC_JSON_ENCODE = 2; TRPC_FLATBUFFER_ENCODE = 3; TRPC_NOOP_ENCODE = 4; } enum TrpcCompressType { TRPC_DEFAULT_COMPRESS = 0; TRPC_GZIP_COMPRESS = 1; TRPC_SNAPPY_COMPRESS = 2; // srpc framework support zlib and lz4 TRPC_ZLIB_COMPRESS = 3; TRPC_LZ4_COMPRESS = 4; } enum TrpcRetCode { TRPC_INVOKE_SUCCESS = 0; TRPC_SERVER_DECODE_ERR = 1; TRPC_SERVER_ENCODE_ERR = 2; TRPC_SERVER_NOSERVICE_ERR = 11; TRPC_SERVER_NOFUNC_ERR = 12; TRPC_SERVER_TIMEOUT_ERR = 21; TRPC_SERVER_OVERLOAD_ERR = 22; TRPC_SERVER_SYSTEM_ERR = 31; TRPC_SERVER_AUTH_ERR = 41; TRPC_CLIENT_INVOKE_TIMEOUT_ERR = 101; TRPC_CLIENT_CONNECT_ERR = 111; TRPC_CLIENT_ENCODE_ERR = 121; TRPC_CLIENT_DECODE_ERR = 122; TRPC_CLIENT_ROUTER_ERR = 131; TRPC_CLINET_NETWORK_ERR = 141; TRPC_INVOKE_UNKNOWN_ERR = 999; } message RequestProtocol { uint32 version = 1; uint32 call_type = 2; uint32 request_id = 3; uint32 timeout = 4; bytes caller = 5; bytes callee = 6; bytes func = 7; uint32 message_type = 8; map trans_info = 9; uint32 content_type = 10; uint32 content_encoding = 11; } message ResponseProtocol { uint32 version = 1; uint32 call_type = 2; uint32 request_id = 3; int32 ret = 4; int32 func_ret = 5; bytes error_msg = 6; uint32 message_type = 7; map trans_info = 8; uint32 content_type = 9; uint32 content_encoding = 10; } sogou-srpc-ac65014/src/module/000077500000000000000000000000001525217424400162225ustar00rootroot00000000000000sogou-srpc-ac65014/src/module/CMakeLists.txt000066400000000000000000000012621525217424400207630ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(module) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(PROTO_LIST proto/opentelemetry_common.proto proto/opentelemetry_resource.proto proto/opentelemetry_trace.proto proto/opentelemetry_metrics.proto proto/opentelemetry_metrics_service.proto) protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_LIST}) set(SRC rpc_module.cc rpc_trace_module.cc rpc_metrics_module.cc rpc_trace_filter.cc rpc_metrics_filter.cc ${PROTO_SRCS} ${PROTO_HDRS} ) add_library(${PROJECT_NAME} OBJECT ${SRC}) if (WIN32) target_compile_definitions( ${PROJECT_NAME} PRIVATE strdup=_strdup strcasecmp=_stricmp strncasecmp=_strnicmp ) endif () sogou-srpc-ac65014/src/module/proto/000077500000000000000000000000001525217424400173655ustar00rootroot00000000000000sogou-srpc-ac65014/src/module/proto/opentelemetry_common.proto000066400000000000000000000061311525217424400247170ustar00rootroot00000000000000// Copyright 2019, OpenTelemetry Authors // // 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. syntax = "proto3"; package opentelemetry.proto.common.v1; // AnyValue is used to represent any type of attribute value. AnyValue may contain a // primitive value such as a string or integer or it may contain an arbitrary nested // object containing arrays, key-value lists and primitives. message AnyValue { // The value is one of the listed fields. It is valid for all values to be unspecified // in which case this AnyValue is considered to be "null". oneof value { string string_value = 1; bool bool_value = 2; int64 int_value = 3; double double_value = 4; ArrayValue array_value = 5; KeyValueList kvlist_value = 6; bytes bytes_value = 7; } } // ArrayValue is a list of AnyValue messages. We need ArrayValue as a message // since oneof in AnyValue does not allow repeated fields. message ArrayValue { // Array of values. The array may be empty (contain 0 elements). repeated AnyValue values = 1; } // KeyValueList is a list of KeyValue messages. We need KeyValueList as a message // since `oneof` in AnyValue does not allow repeated fields. Everywhere else where we need // a list of KeyValue messages (e.g. in Span) we use `repeated KeyValue` directly to // avoid unnecessary extra wrapping (which slows down the protocol). The 2 approaches // are semantically equivalent. message KeyValueList { // A collection of key/value pairs of key-value pairs. The list may be empty (may // contain 0 elements). // The keys MUST be unique (it is not allowed to have more than one // value with the same key). repeated KeyValue values = 1; } // KeyValue is a key-value pair that is used to store Span attributes, Link // attributes, etc. message KeyValue { string key = 1; AnyValue value = 2; } // StringKeyValue is a pair of key/value strings. This is the simpler (and faster) version // of KeyValue that only supports string values. message StringKeyValue { option deprecated = true; string key = 1; string value = 2; } ////////// deprecated // InstrumentationLibrary is a message representing the instrumentation library information // such as the fully qualified name and version. message InstrumentationLibrary { // An empty instrumentation library name means the name is unknown. string name = 1; string version = 2; } // such as the fully qualified name and version. message InstrumentationScope { // An empty instrumentation scope name means the name is unknown. string name = 1; string version = 2; repeated KeyValue attributes = 3; uint32 dropped_attributes_count = 4; } sogou-srpc-ac65014/src/module/proto/opentelemetry_metrics.proto000066400000000000000000000677641525217424400251200ustar00rootroot00000000000000// Copyright 2019, OpenTelemetry Authors // // 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. syntax = "proto3"; package opentelemetry.proto.metrics.v1; import "opentelemetry_common.proto"; import "opentelemetry_resource.proto"; // MetricsData represents the metrics data that can be stored in a persistent // storage, OR can be embedded by other protocols that transfer OTLP metrics // data but do not implement the OTLP protocol. // // The main difference between this message and collector protocol is that // in this message there will not be any "control" or "metadata" specific to // OTLP protocol. // // When new fields are added into this message, the OTLP request MUST be updated // as well. message MetricsData { // An array of ResourceMetrics. // For data coming from a single resource this array will typically contain // one element. Intermediary nodes that receive data from multiple origins // typically batch the data before forwarding further and in that case this // array will contain multiple elements. repeated ResourceMetrics resource_metrics = 1; } ////// deprecated: A collection of InstrumentationLibraryMetrics from a Resource. // A collection of ScopeMetrics from a Resource. message ResourceMetrics { reserved 1000; // The resource for the metrics in this message. // If this field is not set then no resource info is known. opentelemetry.proto.resource.v1.Resource resource = 1; ////// deprecated //repeated InstrumentationLibraryMetrics instrumentation_library_metrics = 2; // A list of metrics that originate from a resource. repeated ScopeMetrics scope_metrics = 2; // This schema_url applies to the data in the "resource" field. It does not apply // to the data in the "scope_metrics" field which have their own schema_url field. string schema_url = 3; } ///// deprecated: A collection of Metrics produced by an InstrumentationLibrary. //message InstrumentationLibraryMetrics { // The instrumentation library information for the metrics in this message. // Semantically when InstrumentationLibrary isn't set, it is equivalent with // an empty instrumentation library name (unknown). // opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = 1; // A list of metrics that originate from an instrumentation library. // repeated Metric metrics = 2; // This schema_url applies to all metrics in the "metrics" field. // string schema_url = 3; //} // A collection of Metrics produced by an Scope. message ScopeMetrics { // The instrumentation scope information for the metrics in this message. // Semantically when InstrumentationScope isn't set, it is equivalent with // an empty instrumentation scope name (unknown). opentelemetry.proto.common.v1.InstrumentationScope scope = 1; // A list of metrics that originate from an instrumentation library. repeated Metric metrics = 2; // This schema_url applies to all metrics in the "metrics" field. string schema_url = 3; } // Defines a Metric which has one or more timeseries. The following is a // brief summary of the Metric data model. For more details, see: // // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/data-model.md // // // The data model and relation between entities is shown in the // diagram below. Here, "DataPoint" is the term used to refer to any // one of the specific data point value types, and "points" is the term used // to refer to any one of the lists of points contained in the Metric. // // - Metric is composed of a metadata and data. // - Metadata part contains a name, description, unit. // - Data is one of the possible types (Sum, Gauge, Histogram, Summary). // - DataPoint contains timestamps, attributes, and one of the possible value type // fields. // // Metric // +------------+ // |name | // |description | // |unit | +------------------------------------+ // |data |---> |Gauge, Sum, Histogram, Summary, ... | // +------------+ +------------------------------------+ // // Data [One of Gauge, Sum, Histogram, Summary, ...] // +-----------+ // |... | // Metadata about the Data. // |points |--+ // +-----------+ | // | +---------------------------+ // | |DataPoint 1 | // v |+------+------+ +------+ | // +-----+ ||label |label |...|label | | // | 1 |-->||value1|value2|...|valueN| | // +-----+ |+------+------+ +------+ | // | . | |+-----+ | // | . | ||value| | // | . | |+-----+ | // | . | +---------------------------+ // | . | . // | . | . // | . | . // | . | +---------------------------+ // | . | |DataPoint M | // +-----+ |+------+------+ +------+ | // | M |-->||label |label |...|label | | // +-----+ ||value1|value2|...|valueN| | // |+------+------+ +------+ | // |+-----+ | // ||value| | // |+-----+ | // +---------------------------+ // // Each distinct type of DataPoint represents the output of a specific // aggregation function, the result of applying the DataPoint's // associated function of to one or more measurements. // // All DataPoint types have three common fields: // - Attributes includes key-value pairs associated with the data point // - TimeUnixNano is required, set to the end time of the aggregation // - StartTimeUnixNano is optional, but strongly encouraged for DataPoints // having an AggregationTemporality field, as discussed below. // // Both TimeUnixNano and StartTimeUnixNano values are expressed as // UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // # TimeUnixNano // // This field is required, having consistent interpretation across // DataPoint types. TimeUnixNano is the moment corresponding to when // the data point's aggregate value was captured. // // Data points with the 0 value for TimeUnixNano SHOULD be rejected // by consumers. // // # StartTimeUnixNano // // StartTimeUnixNano in general allows detecting when a sequence of // observations is unbroken. This field indicates to consumers the // start time for points with cumulative and delta // AggregationTemporality, and it should be included whenever possible // to support correct rate calculation. Although it may be omitted // when the start time is truly unknown, setting StartTimeUnixNano is // strongly encouraged. message Metric { reserved 4, 6, 8; // name of the metric, including its DNS name prefix. It must be unique. string name = 1; // description of the metric, which can be used in documentation. string description = 2; // unit in which the metric value is reported. Follows the format // described by http://unitsofmeasure.org/ucum.html. string unit = 3; // Data determines the aggregation type (if any) of the metric, what is the // reported value type for the data points, as well as the relatationship to // the time interval over which they are reported. oneof data { Gauge gauge = 5; Sum sum = 7; Histogram histogram = 9; ExponentialHistogram exponential_histogram = 10; Summary summary = 11; } } // Gauge represents the type of a scalar metric that always exports the // "current value" for every data point. It should be used for an "unknown" // aggregation. // // A Gauge does not support different aggregation temporalities. Given the // aggregation is unknown, points cannot be combined using the same // aggregation, regardless of aggregation temporalities. Therefore, // AggregationTemporality is not included. Consequently, this also means // "StartTimeUnixNano" is ignored for all data points. message Gauge { repeated NumberDataPoint data_points = 1; } // Sum represents the type of a scalar metric that is calculated as a sum of all // reported measurements over a time interval. message Sum { repeated NumberDataPoint data_points = 1; // aggregation_temporality describes if the aggregator reports delta changes // since last report time, or cumulative changes since a fixed start time. AggregationTemporality aggregation_temporality = 2; // If "true" means that the sum is monotonic. bool is_monotonic = 3; } // Histogram represents the type of a metric that is calculated by aggregating // as a Histogram of all reported measurements over a time interval. message Histogram { repeated HistogramDataPoint data_points = 1; // aggregation_temporality describes if the aggregator reports delta changes // since last report time, or cumulative changes since a fixed start time. AggregationTemporality aggregation_temporality = 2; } // ExponentialHistogram represents the type of a metric that is calculated by aggregating // as a ExponentialHistogram of all reported double measurements over a time interval. message ExponentialHistogram { repeated ExponentialHistogramDataPoint data_points = 1; // aggregation_temporality describes if the aggregator reports delta changes // since last report time, or cumulative changes since a fixed start time. AggregationTemporality aggregation_temporality = 2; } // Summary metric data are used to convey quantile summaries, // a Prometheus (see: https://prometheus.io/docs/concepts/metric_types/#summary) // and OpenMetrics (see: https://github.com/OpenObservability/OpenMetrics/blob/4dbf6075567ab43296eed941037c12951faafb92/protos/prometheus.proto#L45) // data type. These data points cannot always be merged in a meaningful way. // While they can be useful in some applications, histogram data points are // recommended for new applications. message Summary { repeated SummaryDataPoint data_points = 1; } // AggregationTemporality defines how a metric aggregator reports aggregated // values. It describes how those values relate to the time interval over // which they are aggregated. enum AggregationTemporality { // UNSPECIFIED is the default AggregationTemporality, it MUST not be used. AGGREGATION_TEMPORALITY_UNSPECIFIED = 0; // DELTA is an AggregationTemporality for a metric aggregator which reports // changes since last report time. Successive metrics contain aggregation of // values from continuous and non-overlapping intervals. // // The values for a DELTA metric are based only on the time interval // associated with one measurement cycle. There is no dependency on // previous measurements like is the case for CUMULATIVE metrics. // // For example, consider a system measuring the number of requests that // it receives and reports the sum of these requests every second as a // DELTA metric: // // 1. The system starts receiving at time=t_0. // 2. A request is received, the system measures 1 request. // 3. A request is received, the system measures 1 request. // 4. A request is received, the system measures 1 request. // 5. The 1 second collection cycle ends. A metric is exported for the // number of requests received over the interval of time t_0 to // t_0+1 with a value of 3. // 6. A request is received, the system measures 1 request. // 7. A request is received, the system measures 1 request. // 8. The 1 second collection cycle ends. A metric is exported for the // number of requests received over the interval of time t_0+1 to // t_0+2 with a value of 2. AGGREGATION_TEMPORALITY_DELTA = 1; // CUMULATIVE is an AggregationTemporality for a metric aggregator which // reports changes since a fixed start time. This means that current values // of a CUMULATIVE metric depend on all previous measurements since the // start time. Because of this, the sender is required to retain this state // in some form. If this state is lost or invalidated, the CUMULATIVE metric // values MUST be reset and a new fixed start time following the last // reported measurement time sent MUST be used. // // For example, consider a system measuring the number of requests that // it receives and reports the sum of these requests every second as a // CUMULATIVE metric: // // 1. The system starts receiving at time=t_0. // 2. A request is received, the system measures 1 request. // 3. A request is received, the system measures 1 request. // 4. A request is received, the system measures 1 request. // 5. The 1 second collection cycle ends. A metric is exported for the // number of requests received over the interval of time t_0 to // t_0+1 with a value of 3. // 6. A request is received, the system measures 1 request. // 7. A request is received, the system measures 1 request. // 8. The 1 second collection cycle ends. A metric is exported for the // number of requests received over the interval of time t_0 to // t_0+2 with a value of 5. // 9. The system experiences a fault and loses state. // 10. The system recovers and resumes receiving at time=t_1. // 11. A request is received, the system measures 1 request. // 12. The 1 second collection cycle ends. A metric is exported for the // number of requests received over the interval of time t_1 to // t_0+1 with a value of 1. // // Note: Even though, when reporting changes since last report time, using // CUMULATIVE is valid, it is not recommended. This may cause problems for // systems that do not use start_time to determine when the aggregation // value was reset (e.g. Prometheus). AGGREGATION_TEMPORALITY_CUMULATIVE = 2; } // DataPointFlags is defined as a protobuf 'uint32' type and is to be used as a // bit-field representing 32 distinct boolean flags. Each flag defined in this // enum is a bit-mask. To test the presence of a single flag in the flags of // a data point, for example, use an expression like: // // (point.flags & FLAG_NO_RECORDED_VALUE) == FLAG_NO_RECORDED_VALUE // enum DataPointFlags { FLAG_NONE = 0; // This DataPoint is valid but has no recorded value. This value // SHOULD be used to reflect explicitly missing data in a series, as // for an equivalent to the Prometheus "staleness marker". FLAG_NO_RECORDED_VALUE = 1; // Bits 2-31 are reserved for future use. } // NumberDataPoint is a single data point in a timeseries that describes the // time-varying scalar value of a metric. message NumberDataPoint { reserved 1; // The set of key/value pairs that uniquely identify the timeseries from // where this point belongs. The list may be empty (may contain 0 elements). // Attribute keys MUST be unique (it is not allowed to have more than one // attribute with the same key). repeated opentelemetry.proto.common.v1.KeyValue attributes = 7; // StartTimeUnixNano is optional but strongly encouraged, see the // the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 start_time_unix_nano = 2; // TimeUnixNano is required, see the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 time_unix_nano = 3; // The value itself. A point is considered invalid when one of the recognized // value fields is not present inside this oneof. oneof value { double as_double = 4; sfixed64 as_int = 6; } // (Optional) List of exemplars collected from // measurements that were used to form the data point repeated Exemplar exemplars = 5; // Flags that apply to this specific data point. See DataPointFlags // for the available flags and their meaning. uint32 flags = 8; } // HistogramDataPoint is a single data point in a timeseries that describes the // time-varying values of a Histogram. A Histogram contains summary statistics // for a population of values, it may optionally contain the distribution of // those values across a set of buckets. // // If the histogram contains the distribution of values, then both // "explicit_bounds" and "bucket counts" fields must be defined. // If the histogram does not contain the distribution of values, then both // "explicit_bounds" and "bucket_counts" must be omitted and only "count" and // "sum" are known. message HistogramDataPoint { reserved 1; // The set of key/value pairs that uniquely identify the timeseries from // where this point belongs. The list may be empty (may contain 0 elements). // Attribute keys MUST be unique (it is not allowed to have more than one // attribute with the same key). repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; // StartTimeUnixNano is optional but strongly encouraged, see the // the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 start_time_unix_nano = 2; // TimeUnixNano is required, see the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 time_unix_nano = 3; // count is the number of values in the population. Must be non-negative. This // value must be equal to the sum of the "count" fields in buckets if a // histogram is provided. fixed64 count = 4; // sum of the values in the population. If count is zero then this field // must be zero. // // Note: Sum should only be filled out when measuring non-negative discrete // events, and is assumed to be monotonic over the values of these events. // Negative events *can* be recorded, but sum should not be filled out when // doing so. This is specifically to enforce compatibility w/ OpenMetrics, // see: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#histogram double sum = 5; // bucket_counts is an optional field contains the count values of histogram // for each bucket. // // The sum of the bucket_counts must equal the value in the count field. // // The number of elements in bucket_counts array must be by one greater than // the number of elements in explicit_bounds array. repeated fixed64 bucket_counts = 6; // explicit_bounds specifies buckets with explicitly defined bounds for values. // // The boundaries for bucket at index i are: // // (-infinity, explicit_bounds[i]] for i == 0 // (explicit_bounds[i-1], explicit_bounds[i]] for 0 < i < size(explicit_bounds) // (explicit_bounds[i-1], +infinity) for i == size(explicit_bounds) // // The values in the explicit_bounds array must be strictly increasing. // // Histogram buckets are inclusive of their upper boundary, except the last // bucket where the boundary is at infinity. This format is intentionally // compatible with the OpenMetrics histogram definition. repeated double explicit_bounds = 7; // (Optional) List of exemplars collected from // measurements that were used to form the data point repeated Exemplar exemplars = 8; // Flags that apply to this specific data point. See DataPointFlags // for the available flags and their meaning. uint32 flags = 10; // min is the minimum value over (start_time, end_time]. double min = 11; // max is the maximum value over (start_time, end_time]. double max = 12; } // ExponentialHistogramDataPoint is a single data point in a timeseries that describes the // time-varying values of a ExponentialHistogram of double values. A ExponentialHistogram contains // summary statistics for a population of values, it may optionally contain the // distribution of those values across a set of buckets. // message ExponentialHistogramDataPoint { // The set of key/value pairs that uniquely identify the timeseries from // where this point belongs. The list may be empty (may contain 0 elements). // Attribute keys MUST be unique (it is not allowed to have more than one // attribute with the same key). repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; // StartTimeUnixNano is optional but strongly encouraged, see the // the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 start_time_unix_nano = 2; // TimeUnixNano is required, see the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 time_unix_nano = 3; // count is the number of values in the population. Must be // non-negative. This value must be equal to the sum of the "bucket_counts" // values in the positive and negative Buckets plus the "zero_count" field. fixed64 count = 4; // sum of the values in the population. If count is zero then this field // must be zero. // // Note: Sum should only be filled out when measuring non-negative discrete // events, and is assumed to be monotonic over the values of these events. // Negative events *can* be recorded, but sum should not be filled out when // doing so. This is specifically to enforce compatibility w/ OpenMetrics, // see: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#histogram double sum = 5; // scale describes the resolution of the histogram. Boundaries are // located at powers of the base, where: // // base = (2^(2^-scale)) // // The histogram bucket identified by `index`, a signed integer, // contains values that are greater than or equal to (base^index) and // less than (base^(index+1)). // // The positive and negative ranges of the histogram are expressed // separately. Negative values are mapped by their absolute value // into the negative range using the same scale as the positive range. // // scale is not restricted by the protocol, as the permissible // values depend on the range of the data. sint32 scale = 6; // zero_count is the count of values that are either exactly zero or // within the region considered zero by the instrumentation at the // tolerated degree of precision. This bucket stores values that // cannot be expressed using the standard exponential formula as // well as values that have been rounded to zero. // // Implementations MAY consider the zero bucket to have probability // mass equal to (zero_count / count). fixed64 zero_count = 7; // positive carries the positive range of exponential bucket counts. Buckets positive = 8; // negative carries the negative range of exponential bucket counts. Buckets negative = 9; // Buckets are a set of bucket counts, encoded in a contiguous array // of counts. message Buckets { // Offset is the bucket index of the first entry in the bucket_counts array. // // Note: This uses a varint encoding as a simple form of compression. sint32 offset = 1; // Count is an array of counts, where count[i] carries the count // of the bucket at index (offset+i). count[i] is the count of // values greater than or equal to base^(offset+i) and less than // base^(offset+i+1). // // Note: By contrast, the explicit HistogramDataPoint uses // fixed64. This field is expected to have many buckets, // especially zeros, so uint64 has been selected to ensure // varint encoding. repeated uint64 bucket_counts = 2; } // Flags that apply to this specific data point. See DataPointFlags // for the available flags and their meaning. uint32 flags = 10; // (Optional) List of exemplars collected from // measurements that were used to form the data point repeated Exemplar exemplars = 11; // min is the minimum value over (start_time, end_time]. double min = 12; // max is the maximum value over (start_time, end_time]. double max = 13; } // SummaryDataPoint is a single data point in a timeseries that describes the // time-varying values of a Summary metric. message SummaryDataPoint { reserved 1; // The set of key/value pairs that uniquely identify the timeseries from // where this point belongs. The list may be empty (may contain 0 elements). // Attribute keys MUST be unique (it is not allowed to have more than one // attribute with the same key). repeated opentelemetry.proto.common.v1.KeyValue attributes = 7; // StartTimeUnixNano is optional but strongly encouraged, see the // the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 start_time_unix_nano = 2; // TimeUnixNano is required, see the detailed comments above Metric. // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 time_unix_nano = 3; // count is the number of values in the population. Must be non-negative. fixed64 count = 4; // sum of the values in the population. If count is zero then this field // must be zero. // // Note: Sum should only be filled out when measuring non-negative discrete // events, and is assumed to be monotonic over the values of these events. // Negative events *can* be recorded, but sum should not be filled out when // doing so. This is specifically to enforce compatibility w/ OpenMetrics, // see: https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#summary double sum = 5; // Represents the value at a given quantile of a distribution. // // To record Min and Max values following conventions are used: // - The 1.0 quantile is equivalent to the maximum value observed. // - The 0.0 quantile is equivalent to the minimum value observed. // // See the following issue for more context: // https://github.com/open-telemetry/opentelemetry-proto/issues/125 message ValueAtQuantile { // The quantile of a distribution. Must be in the interval // [0.0, 1.0]. double quantile = 1; // The value at the given quantile of a distribution. // // Quantile values must NOT be negative. double value = 2; } // (Optional) list of values at different quantiles of the distribution calculated // from the current snapshot. The quantiles must be strictly increasing. repeated ValueAtQuantile quantile_values = 6; // Flags that apply to this specific data point. See DataPointFlags // for the available flags and their meaning. uint32 flags = 8; } // A representation of an exemplar, which is a sample input measurement. // Exemplars also hold information about the environment when the measurement // was recorded, for example the span and trace ID of the active span when the // exemplar was recorded. message Exemplar { reserved 1; // The set of key/value pairs that were filtered out by the aggregator, but // recorded alongside the original measurement. Only key/value pairs that were // filtered out by the aggregator should be included repeated opentelemetry.proto.common.v1.KeyValue filtered_attributes = 7; // time_unix_nano is the exact time when this exemplar was recorded // // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January // 1970. fixed64 time_unix_nano = 2; // The value of the measurement that was recorded. An exemplar is // considered invalid when one of the recognized value fields is not present // inside this oneof. oneof value { double as_double = 3; sfixed64 as_int = 6; } // (Optional) Span ID of the exemplar trace. // span_id may be missing if the measurement is not recorded inside a trace // or if the trace is not sampled. bytes span_id = 4; // (Optional) Trace ID of the exemplar trace. // trace_id may be missing if the measurement is not recorded inside a trace // or if the trace is not sampled. bytes trace_id = 5; } sogou-srpc-ac65014/src/module/proto/opentelemetry_metrics_service.proto000066400000000000000000000031171525217424400266160ustar00rootroot00000000000000// Copyright 2019, OpenTelemetry Authors // // 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. syntax = "proto3"; package opentelemetry.proto.collector.metrics.v1; import "opentelemetry_metrics.proto"; // Service that can be used to push metrics between one Application // instrumented with OpenTelemetry and a collector, or between a collector and a // central collector. service MetricsService { // For performance reasons, it is recommended to keep this RPC // alive for the entire life of the application. rpc Export(ExportMetricsServiceRequest) returns (ExportMetricsServiceResponse) {} } message ExportMetricsServiceRequest { // An array of ResourceMetrics. // For data coming from a single resource this array will typically contain one // element. Intermediary nodes (such as OpenTelemetry Collector) that receive // data from multiple origins typically batch the data before forwarding further and // in that case this array will contain multiple elements. repeated opentelemetry.proto.metrics.v1.ResourceMetrics resource_metrics = 1; } message ExportMetricsServiceResponse { } sogou-srpc-ac65014/src/module/proto/opentelemetry_resource.proto000066400000000000000000000021621525217424400252560ustar00rootroot00000000000000// Copyright 2019, OpenTelemetry Authors // // 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. syntax = "proto3"; package opentelemetry.proto.resource.v1; import "opentelemetry_common.proto"; // Resource information. message Resource { // Set of attributes that describe the resource. // Attribute keys MUST be unique (it is not allowed to have more than one // attribute with the same key). repeated opentelemetry.proto.common.v1.KeyValue attributes = 1; // dropped_attributes_count is the number of dropped attributes. If the value is 0, then // no attributes were dropped. uint32 dropped_attributes_count = 2; } sogou-srpc-ac65014/src/module/proto/opentelemetry_trace.proto000066400000000000000000000274221525217424400245330ustar00rootroot00000000000000// Copyright 2019, OpenTelemetry Authors // // 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. syntax = "proto3"; package opentelemetry.proto.trace.v1; import "opentelemetry_common.proto"; import "opentelemetry_resource.proto"; // A collection of InstrumentationLibrarySpans from a Resource. // TracesData represents the traces data that can be stored in a persistent storage, // OR can be embedded by other protocols that transfer OTLP traces data but do // not implement the OTLP protocol. // // The main difference between this message and collector protocol is that // in this message there will not be any "control" or "metadata" specific to // OTLP protocol. // // When new fields are added into this message, the OTLP request MUST be updated // as well. message TracesData { // An array of ResourceSpans. // For data coming from a single resource this array will typically contain // one element. Intermediary nodes that receive data from multiple origins // typically batch the data before forwarding further and in that case this // array will contain multiple elements. repeated ResourceSpans resource_spans = 1; } // A collection of ScopeSpans from a Resource. message ResourceSpans { // The resource for the spans in this message. // If this field is not set then no resource info is known. opentelemetry.proto.resource.v1.Resource resource = 1; // A list of InstrumentationLibrarySpans that originate from a resource. repeated InstrumentationLibrarySpans instrumentation_library_spans = 2; // This schema_url applies to the data in the "resource" field. It does not apply // to the data in the "instrumentation_library_spans" field which have their own // schema_url field. string schema_url = 3; } // A collection of Spans produced by an InstrumentationLibrary. message InstrumentationLibrarySpans { // The instrumentation library information for the spans in this message. // Semantically when InstrumentationLibrary isn't set, it is equivalent with // an empty instrumentation library name (unknown). opentelemetry.proto.common.v1.InstrumentationLibrary instrumentation_library = 1; // A list of Spans that originate from an instrumentation library. repeated Span spans = 2; // This schema_url applies to all spans and span events in the "spans" field. string schema_url = 3; } // Span represents a single operation within a trace. Spans can be // nested to form a trace tree. Spans may also be linked to other spans // from the same or different trace and form graphs. Often, a trace // contains a root span that describes the end-to-end latency, and one // or more subspans for its sub-operations. A trace can also contain // multiple root spans, or none at all. Spans do not need to be // contiguous - there may be gaps or overlaps between spans in a trace. // // The next available field id is 17. message Span { // A unique identifier for a trace. All spans from the same trace share // the same `trace_id`. The ID is a 16-byte array. An ID with all zeroes // is considered invalid. // // This field is semantically required. Receiver should generate new // random trace_id if empty or invalid trace_id was received. // // This field is required. bytes trace_id = 1; // A unique identifier for a span within a trace, assigned when the span // is created. The ID is an 8-byte array. An ID with all zeroes is considered // invalid. // // This field is semantically required. Receiver should generate new // random span_id if empty or invalid span_id was received. // // This field is required. bytes span_id = 2; // trace_state conveys information about request position in multiple distributed tracing graphs. // It is a trace_state in w3c-trace-context format: https://www.w3.org/TR/trace-context/#tracestate-header // See also https://github.com/w3c/distributed-tracing for more details about this field. string trace_state = 3; // The `span_id` of this span's parent span. If this is a root span, then this // field must be empty. The ID is an 8-byte array. bytes parent_span_id = 4; // A description of the span's operation. // // For example, the name can be a qualified method name or a file name // and a line number where the operation is called. A best practice is to use // the same display name at the same call point in an application. // This makes it easier to correlate spans in different traces. // // This field is semantically required to be set to non-empty string. // When null or empty string received - receiver may use string "name" // as a replacement. There might be smarted algorithms implemented by // receiver to fix the empty span name. // // This field is required. string name = 5; // SpanKind is the type of span. Can be used to specify additional relationships between spans // in addition to a parent/child relationship. enum SpanKind { // Unspecified. Do NOT use as default. // Implementations MAY assume SpanKind to be INTERNAL when receiving UNSPECIFIED. SPAN_KIND_UNSPECIFIED = 0; // Indicates that the span represents an internal operation within an application, // as opposed to an operation happening at the boundaries. Default value. SPAN_KIND_INTERNAL = 1; // Indicates that the span covers server-side handling of an RPC or other // remote network request. SPAN_KIND_SERVER = 2; // Indicates that the span describes a request to some remote service. SPAN_KIND_CLIENT = 3; // Indicates that the span describes a producer sending a message to a broker. // Unlike CLIENT and SERVER, there is often no direct critical path latency relationship // between producer and consumer spans. A PRODUCER span ends when the message was accepted // by the broker while the logical processing of the message might span a much longer time. SPAN_KIND_PRODUCER = 4; // Indicates that the span describes consumer receiving a message from a broker. // Like the PRODUCER kind, there is often no direct critical path latency relationship // between producer and consumer spans. SPAN_KIND_CONSUMER = 5; } // Distinguishes between spans generated in a particular context. For example, // two spans with the same name may be distinguished using `CLIENT` (caller) // and `SERVER` (callee) to identify queueing latency associated with the span. SpanKind kind = 6; // start_time_unix_nano is the start time of the span. On the client side, this is the time // kept by the local machine where the span execution starts. On the server side, this // is the time when the server's application handler starts running. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // This field is semantically required and it is expected that end_time >= start_time. fixed64 start_time_unix_nano = 7; // end_time_unix_nano is the end time of the span. On the client side, this is the time // kept by the local machine where the span execution ends. On the server side, this // is the time when the server application handler stops running. // Value is UNIX Epoch time in nanoseconds since 00:00:00 UTC on 1 January 1970. // // This field is semantically required and it is expected that end_time >= start_time. fixed64 end_time_unix_nano = 8; // attributes is a collection of key/value pairs. The value can be a string, // an integer, a double or the Boolean values `true` or `false`. Note, global attributes // like server name can be set using the resource API. Examples of attributes: // // "/http/user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36" // "/http/server_latency": 300 // "abc.com/myattribute": true // "abc.com/score": 10.239 repeated opentelemetry.proto.common.v1.KeyValue attributes = 9; // dropped_attributes_count is the number of attributes that were discarded. Attributes // can be discarded because their keys are too long or because there are too many // attributes. If this value is 0, then no attributes were dropped. uint32 dropped_attributes_count = 10; // Event is a time-stamped annotation of the span, consisting of user-supplied // text description and key-value pairs. message Event { // time_unix_nano is the time the event occurred. fixed64 time_unix_nano = 1; // name of the event. // This field is semantically required to be set to non-empty string. string name = 2; // attributes is a collection of attribute key/value pairs on the event. repeated opentelemetry.proto.common.v1.KeyValue attributes = 3; // dropped_attributes_count is the number of dropped attributes. If the value is 0, // then no attributes were dropped. uint32 dropped_attributes_count = 4; } // events is a collection of Event items. repeated Event events = 11; // dropped_events_count is the number of dropped events. If the value is 0, then no // events were dropped. uint32 dropped_events_count = 12; // A pointer from the current span to another span in the same trace or in a // different trace. For example, this can be used in batching operations, // where a single batch handler processes multiple requests from different // traces or when the handler receives a request from a different project. message Link { // A unique identifier of a trace that this linked span is part of. The ID is a // 16-byte array. bytes trace_id = 1; // A unique identifier for the linked span. The ID is an 8-byte array. bytes span_id = 2; // The trace_state associated with the link. string trace_state = 3; // attributes is a collection of attribute key/value pairs on the link. repeated opentelemetry.proto.common.v1.KeyValue attributes = 4; // dropped_attributes_count is the number of dropped attributes. If the value is 0, // then no attributes were dropped. uint32 dropped_attributes_count = 5; } // links is a collection of Links, which are references from this span to a span // in the same or different trace. repeated Link links = 13; // dropped_links_count is the number of dropped links after the maximum size was // enforced. If this value is 0, then no links were dropped. uint32 dropped_links_count = 14; // An optional final status for this span. Semantically when Status isn't set, it means // span's status code is unset, i.e. assume STATUS_CODE_UNSET (code = 0). Status status = 15; } // The Status type defines a logical error model that is suitable for different // programming environments, including REST APIs and RPC APIs. message Status { // IMPORTANT: Backward compatibility notes: // The deprecated status code. This is an optional field. // DeprecatedStatusCode deprecated_code = 1 [deprecated=true]; // A developer-facing human readable error message. string message = 2; // For the semantics of status codes see // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md#set-status enum StatusCode { // The default status. STATUS_CODE_UNSET = 0; // The Span has been validated by an Application developers or Operator to have // completed successfully. STATUS_CODE_OK = 1; // The Span contains an error. STATUS_CODE_ERROR = 2; }; // The status code. StatusCode code = 3; } sogou-srpc-ac65014/src/module/rpc_filter.h000066400000000000000000000066711525217424400205360ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #ifndef __RPC_FILTER_H__ #define __RPC_FILTER_H__ #include #include #include #include "workflow/WFTask.h" #include "workflow/WFTaskFactory.h" namespace srpc { // for transfer between client-server static constexpr const char *OTLP_TRACE_PARENT = "traceparent"; static constexpr const char *OTLP_TRACE_STATE = "tracestate"; static constexpr const char *OTLP_AFFINITY_ATTRIBUTE = "affinity_attribute"; static constexpr size_t OTLP_TRACE_VERSION_SIZE = 2; static constexpr char const *OTLP_TRACE_VERSION = "trace_version"; static constexpr char const *OTLP_TRACE_FLAG = "trace_flag"; // for report static constexpr unsigned int OTLP_HTTP_REDIRECT_MAX = 0; static constexpr unsigned int OTLP_HTTP_RETRY_MAX = 1; static constexpr const char *OTLP_SERVICE_NAME = "rpc.service"; static constexpr const char *OTLP_METHOD_NAME = "rpc.method"; static constexpr const char *SRPC_HTTP_METHOD = "http.method"; static constexpr const char *SRPC_HTTP_STATUS_CODE = "http.status_code"; static constexpr size_t RPC_REPORT_THREHOLD_DEFAULT = 100; static constexpr size_t RPC_REPORT_INTERVAL_DEFAULT = 1000; /* msec */ static constexpr const char *SRPC_MODULE_DATA = "srpc_module_data"; using RPCModuleData = std::map; static RPCModuleData global_empty_map; class RPCFilter { public: SubTask *create_filter_task(const RPCModuleData& data) { if (this->filter(const_cast(data))) return this->create(const_cast(data)); return WFTaskFactory::create_empty_task(); } private: virtual SubTask *create(RPCModuleData& data) { return WFTaskFactory::create_empty_task(); } virtual bool filter(RPCModuleData& data) { return false; } public: RPCFilter(enum RPCModuleType module_type) { this->module_type = module_type; } RPCFilter(const std::string name, enum RPCModuleType module_type) { size_t pos = name.find("::"); if (pos != std::string::npos) this->filter_name = name.substr(0, pos) + "::"; else this->filter_name = name + "::"; this->module_type = module_type; } virtual ~RPCFilter() { } enum RPCModuleType get_module_type() const { return this->module_type; } const std::string& get_name() const { return this->filter_name; } virtual bool client_begin(SubTask *task, RPCModuleData& data) { return true; } virtual bool server_begin(SubTask *task, RPCModuleData& data) { return true; } virtual bool client_end(SubTask *task, RPCModuleData& data) { return true; } virtual bool server_end(SubTask *task, RPCModuleData& data) { return true; } static const std::string raw_var_name(const std::string& name) { size_t pos = name.find("::"); if (pos != std::string::npos) return name.substr(pos + 2); else return name; } private: enum RPCModuleType module_type; std::string filter_name; }; } // end namespace srpc #endif sogou-srpc-ac65014/src/module/rpc_metrics_filter.cc000066400000000000000000000567141525217424400224250ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include "workflow/HttpMessage.h" #include "workflow/HttpUtil.h" #include "workflow/WFTaskFactory.h" #include "workflow/WFHttpServer.h" #include "rpc_basic.h" #include "rpc_var.h" #include "rpc_metrics_filter.h" #include "opentelemetry_metrics_service.pb.h" namespace srpc { static constexpr size_t METRICS_DEFAULT_MAX_AGE = 60; static constexpr size_t METRICS_DEFAULT_AGE_BUCKET = 5; static constexpr const char *METRICS_PULL_METRICS_PATH = "/metrics"; static constexpr const char *OTLP_METRICS_PATH = "/v1/metrics"; static constexpr const char *METRICS_REQUEST_COUNT = "total_request_count"; static constexpr const char *METRICS_REQUEST_METHOD = "total_request_method"; static constexpr const char *METRICS_REQUEST_LATENCY = "total_request_latency"; //static constexpr const char *METRICS_REQUEST_SIZE = "total_request_size"; //static constexpr const char *METRICS_RESPONSE_SIZE = "total_response_size"; using namespace opentelemetry::proto::collector::metrics::v1; using namespace opentelemetry::proto::metrics::v1; using namespace opentelemetry::proto::common::v1; using namespace opentelemetry::proto::resource::v1; RPCMetricsFilter::RPCMetricsFilter() : RPCFilter(RPCModuleTypeMetrics) { this->create_gauge(METRICS_REQUEST_COUNT, "total request count"); this->create_counter(METRICS_REQUEST_METHOD, "request method statistics"); // this->create_histogram(METRICS_REQUEST_SIZE, "total request body size", // {256, 512, 1024, 16384}); // this->create_histogram(METRICS_RESPONSE_SIZE, "total response body size", // {256, 512, 1024, 16384}); this->create_summary(METRICS_REQUEST_LATENCY, "request latency nano seconds", {{0.5, 0.05}, {0.9, 0.01}}); } RPCMetricsFilter::RPCMetricsFilter(const std::string &name) : RPCFilter(RPCModuleTypeMetrics) { this->create_gauge(METRICS_REQUEST_COUNT, "total request count"); this->create_counter(METRICS_REQUEST_METHOD, "request method statistics"); this->create_summary(METRICS_REQUEST_LATENCY, "request latency nano seconds", {{0.5, 0.05}, {0.9, 0.01}}); } bool RPCMetricsFilter::client_end(SubTask *task, RPCModuleData& data) { this->gauge(METRICS_REQUEST_COUNT)->increase(); this->counter(METRICS_REQUEST_METHOD)->increase( {{"service", data[OTLP_SERVICE_NAME]}, {"method", data[OTLP_METHOD_NAME] }}); this->summary(METRICS_REQUEST_LATENCY)->observe(atoll(data[SRPC_DURATION].data())); return true; } bool RPCMetricsFilter::server_end(SubTask *task, RPCModuleData& data) { this->gauge(METRICS_REQUEST_COUNT)->increase(); this->counter(METRICS_REQUEST_METHOD)->increase( {{"service", data[OTLP_SERVICE_NAME]}, {"method", data[OTLP_METHOD_NAME] }}); this->summary(METRICS_REQUEST_LATENCY)->observe(atoll(data[SRPC_DURATION].data())); return true; } GaugeVar *RPCMetricsFilter::gauge(const std::string& name) { std::string var_name = this->get_name() + name; return RPCVarFactory::gauge(name); } CounterVar *RPCMetricsFilter::counter(const std::string& name) { std::string var_name = this->get_name() + name; return RPCVarFactory::counter(name); } HistogramVar *RPCMetricsFilter::histogram(const std::string& name) { std::string var_name = this->get_name() + name; return RPCVarFactory::histogram(name); } SummaryVar *RPCMetricsFilter::summary(const std::string& name) { std::string var_name = this->get_name() + name; return RPCVarFactory::summary(name); } HistogramCounterVar *RPCMetricsFilter::histogram_counter(const std::string &name) { std::string var_name = this->get_name() + name; return RPCVarFactory::histogram_counter(var_name); } GaugeVar *RPCMetricsFilter::create_gauge(const std::string& str, const std::string& help) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } GaugeVar *gauge = new GaugeVar(name, help); RPCVarLocal::get_instance()->add(name, gauge); return gauge; } CounterVar *RPCMetricsFilter::create_counter(const std::string& str, const std::string& help) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } CounterVar *counter = new CounterVar(name, help); RPCVarLocal::get_instance()->add(name, counter); return counter; } HistogramVar *RPCMetricsFilter::create_histogram(const std::string& str, const std::string& help, const std::vector& bucket) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } HistogramVar *histogram = new HistogramVar(name, help, bucket); RPCVarLocal::get_instance()->add(name, histogram); return histogram; } SummaryVar *RPCMetricsFilter::create_summary(const std::string& str, const std::string& help, const std::vector& quantile) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } SummaryVar *summary = new SummaryVar(name, help, quantile, std::chrono::seconds(METRICS_DEFAULT_MAX_AGE), METRICS_DEFAULT_AGE_BUCKET); RPCVarLocal::get_instance()->add(name, summary); return summary; } SummaryVar *RPCMetricsFilter::create_summary(const std::string& str, const std::string& help, const std::vector& quantile, const std::chrono::milliseconds max_age, int age_bucket) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } SummaryVar *summary = new SummaryVar(name, help, quantile, max_age, age_bucket); RPCVarLocal::get_instance()->add(name, summary); return summary; } HistogramCounterVar *RPCMetricsFilter::create_histogram_counter(const std::string &str, const std::string &help, const std::vector &bucket) { if (RPCVarFactory::check_name_format(str) == false) { errno = EINVAL; return NULL; } std::string name = this->get_name() + str; this->mutex.lock(); const auto it = var_names.insert(name); this->mutex.unlock(); if (!it.second) { errno = EEXIST; return NULL; } HistogramCounterVar *hc = new HistogramCounterVar(name, help, bucket); RPCVarLocal::get_instance()->add(name, hc); return hc; } void RPCMetricsFilter::reduce(std::unordered_map& out) { std::unordered_map::iterator it; RPCVarGlobal *global_var = RPCVarGlobal::get_instance(); global_var->mutex.lock(); for (RPCVarLocal *local : global_var->local_vars) { local->mutex.lock(); for (it = local->vars.begin(); it != local->vars.end(); it++) { if (this->var_names.find(it->first) == this->var_names.end()) continue; if (out.find(it->first) == out.end()) out.insert(std::make_pair(it->first, it->second->create(true))); else out[it->first]->reduce(it->second->get_data(), it->second->get_size()); } local->mutex.unlock(); } global_var->mutex.unlock(); } void RPCMetricsFilter::reset() { std::unordered_map::iterator it; RPCVarGlobal *global_var = RPCVarGlobal::get_instance(); global_var->mutex.lock(); for (RPCVarLocal *local : global_var->local_vars) { local->mutex.lock(); for (it = local->vars.begin(); it != local->vars.end(); it++) { if (this->var_names.find(it->first) == this->var_names.end()) continue; it->second->reset(); } local->mutex.unlock(); } global_var->mutex.unlock(); } RPCMetricsPull::RPCMetricsPull() : collector(this->report_output), server(std::bind(&RPCMetricsPull::pull, this, std::placeholders::_1)) { } bool RPCMetricsPull::init(unsigned short port) { return this->server.start(port) == 0; } void RPCMetricsPull::deinit() { this->server.stop(); } void RPCMetricsPull::pull(WFHttpTask *task) { if (strcmp(task->get_req()->get_request_uri(), METRICS_PULL_METRICS_PATH)) return; this->mutex.lock(); this->expose(); task->get_resp()->append_output_body(std::move(this->report_output)); this->report_output.clear(); this->mutex.unlock(); } bool RPCMetricsPull::expose() { std::unordered_map tmp; std::unordered_map::iterator it; std::string output; this->reduce(tmp); for (it = tmp.begin(); it != tmp.end(); it++) { RPCVar *var = it->second; this->report_output += "# HELP " + var->get_name() + " " + var->get_help() + "\n# TYPE " + var->get_name() + " " + var->get_type_str() + "\n"; output.clear(); var->collect(&this->collector); this->report_output += output; } for (it = tmp.begin(); it != tmp.end(); it++) delete it->second; return true; } void RPCMetricsPull::Collector::collect_gauge(RPCVar *gauge, double data) { this->report_output += gauge->get_name() + " " + std::to_string(data) + "\n"; } void RPCMetricsPull::Collector::collect_counter_each(RPCVar *counter, const std::string& label, double data) { this->report_output += counter->get_name() + "{" + label + "} " + std::to_string(data) + "\n"; } void RPCMetricsPull::Collector::collect_histogram_each(RPCVar *histogram, double bucket_boundary, size_t current_count) { this->report_output += histogram->get_name(); if (bucket_boundary != DBL_MAX) { this->report_output += "_bucket{le=\"" + std::to_string(bucket_boundary) + "\"} "; } else this->report_output += "_bucket{le=\"+Inf\"} "; this->report_output += std::to_string(current_count) + "\n"; } void RPCMetricsPull::Collector::collect_histogram_end(RPCVar *histogram, double sum, size_t count) { this->report_output += histogram->get_name() + "_sum " + std::to_string(sum) + "\n"; this->report_output += histogram->get_name() + "_count " + std::to_string(count) + "\n"; } void RPCMetricsPull::Collector::collect_summary_each(RPCVar *summary, double quantile, double quantile_out) { this->report_output += summary->get_name() + "{quantile=\"" + std::to_string(quantile) + "\"} "; if (quantile_out == 0) this->report_output += "NaN"; else this->report_output += std::to_string(quantile_out); this->report_output += "\n"; } void RPCMetricsPull::Collector::collect_summary_end(RPCVar *summary, double sum, size_t count) { this->report_output += summary->get_name() + "_sum " + std::to_string(sum) + "\n"; this->report_output += summary->get_name() + "_count " + std::to_string(count) + "\n"; } bool RPCFilterPolicy::report(size_t count) { bool ret = false; long long timestamp = GET_CURRENT_MS(); if (this->last_report_timestamp == 0) this->last_report_timestamp = timestamp; if ((timestamp > this->last_report_timestamp + (long long)this->report_interval || count >= this->report_threshold) && this->reporting == false) { this->mutex.lock(); if (this->reporting == false) { this->reporting = true; this->last_report_timestamp = timestamp; ret = true; } this->mutex.unlock(); } return ret; } void RPCFilterPolicy::set_reporting(bool flag) { this->mutex.lock(); this->reporting = flag; this->mutex.unlock(); } RPCMetricsOTel::RPCMetricsOTel(const std::string &name, const std::string& url, unsigned int redirect_max, unsigned int retry_max, size_t report_threshold, size_t report_interval_msec) : RPCMetricsFilter(name), url(url + OTLP_METRICS_PATH), redirect_max(redirect_max), retry_max(retry_max), policy(report_threshold, report_interval_msec), report_counts(0) { } RPCMetricsOTel::RPCMetricsOTel(const std::string &name, const std::string& url) : RPCMetricsFilter(name), url(url + OTLP_METRICS_PATH), redirect_max(OTLP_HTTP_REDIRECT_MAX), retry_max(OTLP_HTTP_RETRY_MAX), policy(RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT), report_counts(0) { } RPCMetricsOTel::Collector::~Collector() { for (const auto& kv : this->label_map) delete kv.second; } void RPCMetricsOTel::add_attributes(const std::string& key, const std::string& value) { this->mutex.lock(); this->attributes.insert(std::make_pair(key, value)); this->mutex.unlock(); } size_t RPCMetricsOTel::clear_attributes() { size_t ret; this->mutex.lock(); ret = this->attributes.size(); this->attributes.clear(); this->mutex.unlock(); return ret; } SubTask *RPCMetricsOTel::create(RPCModuleData& data) { std::string *output = new std::string; ExportMetricsServiceRequest req; ResourceMetrics *rm = req.add_resource_metrics(); Resource *resource = rm->mutable_resource(); ScopeMetrics *metrics = rm->add_scope_metrics(); KeyValue *attribute; AnyValue *value; this->report_counts = 0; // this is not very strict but acceptable InstrumentationScope *scope = metrics->mutable_scope(); scope->set_name(this->scope_name); auto iter = data.find(OTLP_METHOD_NAME); if (iter != data.end()) { attribute = resource->add_attributes(); attribute->set_key(OTLP_METHOD_NAME); value = attribute->mutable_value(); value->set_string_value(iter->second); } this->mutex.lock(); for (const auto& attr : this->attributes) { KeyValue *attribute = resource->add_attributes(); attribute->set_key(attr.first); AnyValue *value = attribute->mutable_value(); value->set_string_value(attr.second); } this->mutex.unlock(); iter = data.find(OTLP_SERVICE_NAME); // if attributes also set service.name, data takes precedence if (iter != data.end()) { attribute = resource->add_attributes(); attribute->set_key(OTLP_SERVICE_NAME); value = attribute->mutable_value(); value->set_string_value(iter->second); } this->expose(metrics); // fprintf(stderr, "[Metrics info to report]\n%s\n", req.DebugString().c_str()); req.SerializeToString(output); this->policy.set_reporting(false); WFHttpTask *task = WFTaskFactory::create_http_task(this->url, this->redirect_max, this->retry_max, [](WFHttpTask *task) { /* protocol::HttpResponse *resp = task->get_resp(); fprintf(stderr, "[metrics report callback] state=%d error=%d\n", task->get_state(), task->get_error()); if (task->get_state() == WFT_STATE_SUCCESS) { fprintf(stderr, "%s %s %s\r\n", resp->get_http_version(), resp->get_status_code(), resp->get_reason_phrase()); } */ delete (std::string *)task->user_data; }); protocol::HttpRequest *http_req = task->get_req(); http_req->set_method(HttpMethodPost); http_req->add_header_pair("Content-Type", "application/x-protobuf"); task->user_data = output; http_req->append_output_body_nocopy(output->c_str(), output->length()); return task; } bool RPCMetricsOTel::expose(google::protobuf::Message *msg) { std::unordered_map tmp; std::unordered_map::iterator it; ScopeMetrics *metrics; Histogram *report_histogram; metrics = static_cast(msg); this->reduce(tmp); this->collector.set_current_nano(GET_CURRENT_NS()); for (it = tmp.begin(); it != tmp.end(); it++) { RPCVar *var = it->second; Metric *m = metrics->add_metrics(); google::protobuf::Message *current_var; m->set_name(RPCFilter::raw_var_name(var->get_name())); m->set_description(var->get_help()); switch(var->get_type()) { default: case VAR_GAUGE: current_var = m->mutable_gauge(); this->collector.collect_gauge(var, current_var); break; case VAR_COUNTER: current_var = m->mutable_sum(); this->collector.collect_counter(var, current_var); break; case VAR_HISTOGRAM: report_histogram = m->mutable_histogram(); current_var = report_histogram->add_data_points(); this->collector.collect_histogram(var, current_var); break; case VAR_SUMMARY: current_var = m->mutable_summary(); this->collector.collect_summary(var, current_var); break; case VAR_HISTOGRAM_COUNTER: // add multiple metrics inside this->collector.collect_histogram_counter(var, metrics); break; } } for (it = tmp.begin(); it != tmp.end(); it++) delete it->second; this->reset(); // reset by report interval return true; } void RPCMetricsOTel::Collector::collect_gauge(RPCVar *var, google::protobuf::Message *msg) { GaugeVar *gauge = (GaugeVar *)var; Gauge *report_gauge = static_cast(msg); double data = gauge->get(); NumberDataPoint *data_points = report_gauge->add_data_points(); data_points->set_as_double(data); data_points->set_time_unix_nano(this->current_timestamp_nano); } void RPCMetricsOTel::Collector::collect_counter(RPCVar *var, google::protobuf::Message *msg) { CounterVar *counter = (CounterVar *)var; std::unordered_map *data; data = (std::unordered_map *)counter->get_map(); for (auto it = data->begin(); it != data->end(); it++) this->collect_counter_each(it->first, it->second->get(), msg); } void RPCMetricsOTel::Collector::add_counter_label(const std::string& label) { const char *key; const char *value; size_t key_len; size_t lpos; size_t rpos; size_t pos = -1; LABEL_MAP *m = new LABEL_MAP(); do { pos++; lpos = label.find_first_of('=', pos); key = label.data() + pos; key_len = lpos - pos; rpos = label.find_first_of(',', lpos + 1); pos = rpos; if (rpos == std::string::npos) rpos = label.length(); value = label.data() + lpos + 2; // value_len = rpos - lpos - 3; m->emplace(std::string{key, key_len}, std::string{value, rpos - lpos - 3}); } while (pos != std::string::npos); this->label_map.emplace(label, m); } void RPCMetricsOTel::Collector::collect_counter_each(const std::string& label, double data, google::protobuf::Message *msg) { Sum *report_sum = static_cast(msg); NumberDataPoint *data_points = report_sum->add_data_points(); std::map::iterator it; std::string key; std::string value; if (!label.empty()) { it = this->label_map.find(label); if (it == this->label_map.end()) { this->add_counter_label(label); it = this->label_map.find(label); } for (const auto& kv : *(it->second)) { KeyValue *attribute = data_points->add_attributes(); attribute->set_key(kv.first); AnyValue *value = attribute->mutable_value(); value->set_string_value(kv.second); } } data_points->set_as_double(data); data_points->set_time_unix_nano(this->current_timestamp_nano); } void RPCMetricsOTel::Collector::collect_histogram(RPCVar *var, google::protobuf::Message *msg) { HistogramVar *histogram = (HistogramVar *)var; HistogramDataPoint *data_points = static_cast(msg); const std::vector *bucket_counts = histogram->get_bucket_counts(); const std::vector *bucket_boundaries = histogram->get_bucket_boundaries(); // begin data_points->set_time_unix_nano(this->current_timestamp_nano); size_t i = 0; size_t current = 0; for (; i < bucket_boundaries->size(); i++) { // current += this->bucket_counts[i]; current = bucket_counts->at(i); this->collect_histogram_each(bucket_boundaries->at(i), current, data_points); } // current += this->bucket_counts[i]; current = bucket_counts->at(i); this->collect_histogram_each(DBL_MAX, current, data_points); // end data_points->set_sum(histogram->get_sum()); data_points->set_count(histogram->get_count()); } void RPCMetricsOTel::Collector::collect_histogram_each(double bucket_boundary, size_t current_count, google::protobuf::Message *msg) { HistogramDataPoint *data_points = static_cast(msg); data_points->add_bucket_counts(current_count); if (bucket_boundary != DBL_MAX) data_points->add_explicit_bounds(bucket_boundary); } void RPCMetricsOTel::Collector::collect_summary(RPCVar *var, google::protobuf::Message *msg) { SummaryVar *summary = (SummaryVar *)var; Summary *report_summary = static_cast(msg); const std::vector& quantiles = summary->get_quantiles(); const std::vector& quantile_out = summary->get_quantile_out(); SummaryDataPoint *data_points = report_summary->add_data_points(); // begin data_points->set_time_unix_nano(this->current_timestamp_nano); for (size_t i = 0; i < quantiles.size(); i++) // equavalant to summary->get_size() { this->collect_summary_each(quantiles[i].quantile, quantile_out[i], data_points); } // end data_points->set_sum(summary->get_sum()); data_points->set_count(summary->get_count()); summary->clear_quantile_out(); } void RPCMetricsOTel::Collector::collect_summary_each(double quantile, double quantile_out, google::protobuf::Message *msg) { SummaryDataPoint *data_points = static_cast(msg); SummaryDataPoint::ValueAtQuantile *vaq = data_points->add_quantile_values(); vaq->set_quantile(quantile); vaq->set_value(quantile_out); } void RPCMetricsOTel::Collector::collect_histogram_counter(RPCVar *var, google::protobuf::Message *msg) { HistogramCounterVar *hc = (HistogramCounterVar *)var; ScopeMetrics *metrics = static_cast(msg); const std::unordered_map *data; data = static_cast *>(hc->get_map()); std::map::iterator m_it; std::string key; std::string value; Metric *m; Histogram *report_histogram; HistogramDataPoint *data_points; std::string label; for (auto it = data->begin(); it != data->end(); it++) { m = metrics->add_metrics(); m->set_name(RPCFilter::raw_var_name(hc->get_name())); m->set_description(hc->get_help()); report_histogram = m->mutable_histogram(); report_histogram->set_aggregation_temporality( AggregationTemporality::AGGREGATION_TEMPORALITY_DELTA); data_points = report_histogram->add_data_points(); label = it->first; m_it = this->label_map.find(label); if (m_it == this->label_map.end()) { this->add_counter_label(label); m_it = this->label_map.find(label); } for (const auto &kv : *(m_it->second)) { KeyValue *attribute = data_points->add_attributes(); attribute->set_key(kv.first); AnyValue *value = attribute->mutable_value(); value->set_string_value(kv.second); } this->collect_histogram(it->second, data_points); } } } // end namespace srpc sogou-srpc-ac65014/src/module/rpc_metrics_filter.h000066400000000000000000000172361525217424400222630ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifndef __RPC_METRIC_FILTER_H__ #define __RPC_METRIC_FILTER_H__ #include #include #include #include #include #include #include #include #include "workflow/WFTask.h" #include "workflow/WFTaskFactory.h" #include "workflow/WFHttpServer.h" #include "rpc_basic.h" #include "rpc_var.h" #include "rpc_metrics_module.h" namespace srpc { class RPCMetricsFilter : public RPCFilter { public: GaugeVar *create_gauge(const std::string& name, const std::string& help); CounterVar *create_counter(const std::string& name, const std::string& help); HistogramVar *create_histogram(const std::string& name, const std::string& help, const std::vector& bucket); SummaryVar *create_summary(const std::string& name, const std::string& help, const std::vector& quantile); SummaryVar *create_summary(const std::string& name, const std::string& help, const std::vector& quantile, const std::chrono::milliseconds max_age, int age_bucket); HistogramCounterVar *create_histogram_counter(const std::string &name, const std::string &help, const std::vector &bucket); // thread local api GaugeVar *gauge(const std::string& name); CounterVar *counter(const std::string& name); HistogramVar *histogram(const std::string& name); SummaryVar *summary(const std::string& name); HistogramCounterVar *histogram_counter(const std::string &name); // filter api bool client_end(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; public: RPCMetricsFilter(); RPCMetricsFilter(const std::string &name); protected: void reduce(std::unordered_map& out); void reset(); protected: std::mutex mutex; std::set var_names; }; class RPCMetricsPull : public RPCMetricsFilter { public: bool init(unsigned short port); // pull port for Prometheus void deinit(); public: RPCMetricsPull(); private: SubTask *create(RPCModuleData& data) override { return WFTaskFactory::create_empty_task(); } bool filter(RPCModuleData& data) override { return false; // will not collect by policy.collect(data); } private: class Collector : public RPCVarCollector { public: Collector(std::string& output) : report_output(output) { } void collect_gauge(RPCVar *gauge, double data) override; void collect_counter_each(RPCVar *counter, const std::string& label, double data) override; void collect_histogram_begin(RPCVar *histogram) override { } void collect_histogram_each(RPCVar *histogram, double bucket_boudary, size_t current_count) override; void collect_histogram_end(RPCVar *histogram, double sum, size_t count) override; void collect_summary_begin(RPCVar *summary) override { } void collect_summary_each(RPCVar *summary, double quantile, double quantile_out) override; void collect_summary_end(RPCVar *summary, double sum, size_t count) override; private: std::string& report_output; }; private: bool expose(); void pull(WFHttpTask *task); std::string report_output; Collector collector; WFHttpServer server; }; class RPCFilterPolicy { public: RPCFilterPolicy(size_t report_threshold, size_t report_interval_msec) : report_threshold(report_threshold), report_interval(report_interval_msec), last_report_timestamp(0), reporting(false) { } void set_report_threshold(size_t threshold) { if (threshold <= 0) threshold = 1; this->report_threshold = threshold; } void set_report_interval(int msec) { if (msec <= 0) msec = 1; this->report_interval = msec; } bool report(size_t count); void set_reporting(bool flag); private: size_t report_threshold; // metrics to report at most size_t report_interval; long long last_report_timestamp; bool reporting; std::mutex mutex; }; class RPCMetricsOTel : public RPCMetricsFilter { public: void set_report_threshold(size_t threshold) { this->policy.set_report_threshold(threshold); } void set_report_interval(int msec) { this->policy.set_report_interval(msec); } // for client level attributes, such as ProviderID void add_attributes(const std::string& key, const std::string& value); size_t clear_attributes(); // refer to 'ScopeMetrics' : a collection of Metrics in a Scope void set_scope_name(const std::string& name) { this->scope_name = name; } public: RPCMetricsOTel(const std::string &name, const std::string &url); RPCMetricsOTel(const std::string &name, const std::string& url, unsigned int redirect_max, unsigned int retry_max, size_t report_threshold, size_t report_interval); private: SubTask *create(RPCModuleData& data) override; bool filter(RPCModuleData& data) override { return this->policy.report(++this->report_counts); } private: class Collector : public RPCVarCollector { public: Collector() { } virtual ~Collector(); void set_current_nano(unsigned long long ns) { this->current_timestamp_nano = ns; } // new api : fill var into msg void collect_gauge(RPCVar *gauge, google::protobuf::Message *msg); void collect_counter(RPCVar *counter, google::protobuf::Message *msg); void collect_histogram(RPCVar *histogram, google::protobuf::Message *msg); void collect_summary(RPCVar *summary, google::protobuf::Message *msg); void collect_histogram_counter(RPCVar *histogram_counter, google::protobuf::Message *msg); void collect_counter_each(const std::string &label, double data, google::protobuf::Message *msg); void collect_histogram_each(double bucket_boudary, size_t current_count, google::protobuf::Message *msg); void collect_summary_each(double quantile, double quantile_out, google::protobuf::Message *msg); // deprecated api void collect_gauge(RPCVar *gauge, double data) override {} void collect_counter_each(RPCVar *counter, const std::string &label, double data) override {} void collect_histogram_begin(RPCVar *histogram) override {} void collect_histogram_each(RPCVar *histogram, double bucket_boudary, size_t current_count) override {} void collect_histogram_end(RPCVar *histogram, double sum, size_t count) override{} void collect_summary_begin(RPCVar *summary) override {} void collect_summary_each(RPCVar *summary, double quantile, double quantile_out) override {} void collect_summary_end(RPCVar *summary, double sum, size_t count) override {} private: void add_counter_label(const std::string& label); private: using LABEL_MAP = std::map; unsigned long long current_timestamp_nano; std::map label_map; }; private: bool expose(google::protobuf::Message *metrics); protected: Collector collector; private: std::string url; int redirect_max; int retry_max; RPCFilterPolicy policy; std::atomic report_counts; std::map attributes; std::string scope_name; }; } // end namespace srpc #endif sogou-srpc-ac65014/src/module/rpc_metrics_module.cc000066400000000000000000000036361525217424400224200ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include "rpc_metrics_module.h" namespace srpc { bool MetricsModule::client_begin(SubTask *task, RPCModuleData& data) { data[SRPC_START_TIMESTAMP] = std::to_string(GET_CURRENT_NS()); // clear other unnecessary module_data since the data comes from series data.erase(SRPC_DURATION); data.erase(SRPC_FINISH_TIMESTAMP); return true; } bool MetricsModule::client_end(SubTask *task, RPCModuleData& data) { if (data.find(SRPC_START_TIMESTAMP) != data.end() && data.find(SRPC_DURATION) == data.end()) { unsigned long long end_time = GET_CURRENT_NS(); data[SRPC_FINISH_TIMESTAMP] = std::to_string(end_time); data[SRPC_DURATION] = std::to_string(end_time - atoll(data[SRPC_START_TIMESTAMP].data())); } return true; } bool MetricsModule::server_begin(SubTask *task, RPCModuleData& data) { if (data.find(SRPC_START_TIMESTAMP) == data.end()) data[SRPC_START_TIMESTAMP] = std::to_string(GET_CURRENT_NS()); return true; } bool MetricsModule::server_end(SubTask *task, RPCModuleData& data) { if (data.find(SRPC_START_TIMESTAMP) != data.end() && data.find(SRPC_DURATION) == data.end()) { unsigned long long end_time = GET_CURRENT_NS(); data[SRPC_FINISH_TIMESTAMP] = std::to_string(end_time); data[SRPC_DURATION] = std::to_string(end_time - atoll(data[SRPC_START_TIMESTAMP].data())); } return true; } } // end namespace srpc sogou-srpc-ac65014/src/module/rpc_metrics_module.h000066400000000000000000000044501525217424400222550ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifndef __RPC_METRICS_MODULE_H__ #define __RPC_METRICS_MODULE_H__ #include "rpc_basic.h" #include "rpc_context.h" #include "rpc_module.h" namespace srpc { // Basic MetricsModlue for generating general metrics data. // Each kind of network task can derived its own MetricsModlue. class MetricsModule : public RPCModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool client_end(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; public: MetricsModule() : RPCModule(RPCModuleTypeMetrics) { } }; // Fill RPC related data in metrics module template class RPCMetricsModule : public MetricsModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; }; ////////// impl template bool RPCMetricsModule::client_begin(SubTask *task, RPCModuleData& data) { MetricsModule::client_begin(task, data); auto *client_task = static_cast(task); auto *req = client_task->get_req(); data[OTLP_SERVICE_NAME] = req->get_service_name(); data[OTLP_METHOD_NAME] = req->get_method_name(); return true; } template bool RPCMetricsModule::server_begin(SubTask *task, RPCModuleData& data) { MetricsModule::server_begin(task, data); auto *server_task = static_cast(task); auto *req = server_task->get_req(); data[OTLP_SERVICE_NAME] = req->get_service_name(); data[OTLP_METHOD_NAME] = req->get_method_name(); return true; } } // end namespace srpc #endif sogou-srpc-ac65014/src/module/rpc_module.cc000066400000000000000000000121111525217424400206560ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #include #include #include "workflow/WFTask.h" #include #include "rpc_basic.h" #include "rpc_module.h" #include "rpc_trace_module.h" namespace srpc { bool RPCModule::client_task_begin(SubTask *task, RPCModuleData& data) { if (!this->client_begin(task, data)) return false; for (RPCFilter *filter : this->filters) { if (!filter->client_begin(task, data)) return false; } return true; } bool RPCModule::server_task_begin(SubTask *task, RPCModuleData& data) { if (!this->server_begin(task, data)) return false; for (RPCFilter *filter : this->filters) { if (!filter->server_begin(task, data)) return false; } return true; } bool RPCModule::client_task_end(SubTask *task, RPCModuleData& data) { SubTask *filter_task; if (!this->client_end(task, data)) return false; for (RPCFilter *filter : this->filters) { if (!filter->client_end(task, data)) return false; filter_task = filter->create_filter_task(data); series_of(task)->push_front(filter_task); } return true; } bool RPCModule::server_task_end(SubTask *task, RPCModuleData& data) { SubTask *filter_task; if (!this->server_end(task, data)) return false; for (RPCFilter *filter : this->filters) { if (!filter->server_end(task, data)) return false; filter_task = filter->create_filter_task(data); series_of(task)->push_front(filter_task); } return true; } SnowFlake::SnowFlake(int timestamp_bits, int group_bits, int machine_bits) { this->timestamp_bits = timestamp_bits; this->group_bits = group_bits; this->machine_bits = machine_bits; this->sequence_bits = SRPC_TOTAL_BITS - timestamp_bits - group_bits - machine_bits; this->last_timestamp = 1L; this->sequence = 0L; this->group_id_max = 1 << this->group_bits; this->machine_id_max = 1 << this->machine_bits; this->sequence_max = 1 << this->sequence_bits; this->machine_shift = this->sequence_bits; this->group_shift = this->machine_shift + this->machine_bits; this->timestamp_shift = this->group_shift + this->group_bits; } bool SnowFlake::get_id(long long group_id, long long machine_id, unsigned long long *uid) { if (group_id > this->group_id_max || machine_id > this->machine_id_max) return false; long long timestamp = GET_CURRENT_MS_STEADY(); long long seq_id; if (timestamp < this->last_timestamp) return false; if (timestamp == this->last_timestamp) { if (++this->sequence > this->sequence_max) return false; // too many sequence_id in one millie second } else this->sequence = 0L; seq_id = this->sequence++; this->last_timestamp = timestamp; *uid = (timestamp << this->timestamp_shift) | (group_id << this->group_shift) | (machine_id << this->machine_shift) | seq_id; return true; } bool CustomModule::client_begin(SubTask *task, RPCModuleData& data) { return true; } bool CustomModule::client_end(SubTask *task, RPCModuleData& data) { return true; } bool CustomModule::server_begin(SubTask *task, RPCModuleData& data) { return true; } bool CustomModule::server_end(SubTask *task, RPCModuleData& data) { return true; } bool http_set_header_module_data(const RPCModuleData& data, protocol::HttpMessage *msg) { auto it = data.begin(); int flag = 0; while (it != data.end() && flag != 3) { if (it->first == SRPC_TRACE_ID) { char trace_id_buf[SRPC_TRACEID_SIZE * 2 + 1]; TRACE_ID_BIN_TO_HEX((uint64_t *)it->second.data(), trace_id_buf); msg->set_header_pair("Trace-Id", trace_id_buf); flag |= 1; } else if (it->first == SRPC_SPAN_ID) { char span_id_buf[SRPC_SPANID_SIZE * 2 + 1]; SPAN_ID_BIN_TO_HEX((uint64_t *)it->second.data(), span_id_buf); msg->set_header_pair("Span-Id", span_id_buf); flag |= (1 << 1); } ++it; } return true; } bool http_get_header_module_data(const protocol::HttpMessage *msg, RPCModuleData& data) { protocol::HttpHeaderCursor cursor(msg); std::string name; std::string value; int flag = 0; while (cursor.next(name, value) && flag != 3) { if (strcasecmp(name.c_str(), "Trace-Id") == 0 && value.size() == SRPC_TRACEID_SIZE * 2) { uint64_t trace_id[2]; TRACE_ID_HEX_TO_BIN(value.data(), trace_id); data[SRPC_TRACE_ID] = std::string((char *)trace_id, SRPC_TRACEID_SIZE); flag |= 1; } else if (strcasecmp(name.c_str(), "Span-Id") == 0 && value.size() == SRPC_SPANID_SIZE * 2) { uint64_t span_id[1]; SPAN_ID_HEX_TO_BIN(value.data(), span_id); data[SRPC_SPAN_ID] = std::string((char *)span_id, SRPC_SPANID_SIZE); flag |= (1 << 1); } } return true; } } // end namespace srpc sogou-srpc-ac65014/src/module/rpc_module.h000066400000000000000000000100341525217424400205220ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #ifndef __RPC_MODULE_H__ #define __RPC_MODULE_H__ #include #include #include #include "workflow/WFTask.h" #include "rpc_basic.h" #include "rpc_filter.h" namespace srpc { static constexpr char const *SRPC_COMPONENT_SRPC = "srpc"; static constexpr char const *SRPC_SPAN_LOG = "srpc.log"; static constexpr char const *SRPC_SPAN_EVENT = "event"; static constexpr char const *SRPC_SPAN_MESSAGE = "message"; static constexpr char const *SRPC_START_TIMESTAMP = "srpc.start_time"; static constexpr char const *SRPC_FINISH_TIMESTAMP = "srpc.finish_time"; static constexpr char const *SRPC_DURATION = "srpc.duration"; static constexpr char const *SRPC_TIMEOUT_REASON = "srpc.timeout_reason"; static constexpr char const *SRPC_SPAN_ID = "srpc.span_id"; static constexpr char const *SRPC_TRACE_ID = "srpc.trace_id"; static constexpr char const *SRPC_PARENT_SPAN_ID = "srpc.parent_span_id"; //for SnowFlake: u_id = [timestamp][group][machine][sequence] static constexpr int SRPC_TIMESTAMP_BITS = 38; static constexpr int SRPC_GROUP_BITS = 4; static constexpr int SRPC_MACHINE_BITS = 10; //static constexpr int SRPC_SEQUENCE_BITS = 12; static constexpr int SRPC_TOTAL_BITS = 64; class RPCModule { protected: virtual bool client_begin(SubTask *task, RPCModuleData& data) = 0; virtual bool server_begin(SubTask *task, RPCModuleData& data) = 0; virtual bool client_end(SubTask *task, RPCModuleData& data) = 0; virtual bool server_end(SubTask *task, RPCModuleData& data) = 0; public: void add_filter(RPCFilter *filter) { this->mutex.lock(); this->filters.push_back(filter); this->mutex.unlock(); } size_t get_filters_size() const { return this->filters.size(); } enum RPCModuleType get_module_type() const { return this->module_type; } bool client_task_begin(SubTask *task, RPCModuleData& data); bool server_task_begin(SubTask *task, RPCModuleData& data); bool client_task_end(SubTask *task, RPCModuleData& data); bool server_task_end(SubTask *task, RPCModuleData& data); RPCModule(enum RPCModuleType module_type) { this->module_type = module_type; } virtual ~RPCModule() {} private: enum RPCModuleType module_type; std::mutex mutex; std::list filters; }; class SnowFlake { public: SnowFlake(int timestamp_bits, int group_bits, int machine_bits); SnowFlake() : SnowFlake(SRPC_TIMESTAMP_BITS, SRPC_GROUP_BITS, SRPC_MACHINE_BITS) { } bool get_id(long long group_id, long long machine_id, unsigned long long *uid); private: std::atomic last_timestamp; // -1L; std::atomic sequence; // 0L; int timestamp_bits; int group_bits; int machine_bits; int sequence_bits; long long group_id_max; long long machine_id_max; long long sequence_max; long long timestamp_shift; long long group_shift; long long machine_shift; }; class CustomModule : public RPCModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool client_end(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; public: CustomModule() : RPCModule(RPCModuleTypeCustom) { } }; template class RPCCustomModule : public CustomModule { }; bool http_get_header_module_data(const protocol::HttpMessage *msg, RPCModuleData& data); bool http_set_header_module_data(const RPCModuleData& data, protocol::HttpMessage *msg); } // end namespace srpc #endif sogou-srpc-ac65014/src/module/rpc_trace_filter.cc000066400000000000000000000340411525217424400220420ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include #include #include "workflow/WFTask.h" #include "workflow/HttpUtil.h" #include "rpc_trace_filter.h" #include "opentelemetry_trace.pb.h" namespace srpc { using namespace opentelemetry::proto::trace::v1; using namespace opentelemetry::proto::common::v1; using namespace opentelemetry::proto::resource::v1; static constexpr char const *SRPC_COMPONENT_OTEL_STR = "rpc.system"; static InstrumentationLibrarySpans * rpc_span_fill_pb_request(const RPCModuleData& data, const std::unordered_map& attributes, TracesData *req) { ResourceSpans *rs = req->add_resource_spans(); InstrumentationLibrarySpans *spans = rs->add_instrumentation_library_spans(); Resource *resource = rs->mutable_resource(); KeyValue *attribute; AnyValue *value; auto iter = data.find(OTLP_METHOD_NAME); if (iter != data.end()) { attribute = resource->add_attributes(); attribute->set_key(OTLP_METHOD_NAME); value = attribute->mutable_value(); value->set_string_value(iter->second); } for (const auto& attr : attributes) { KeyValue *attribute = resource->add_attributes(); attribute->set_key(attr.first); AnyValue *value = attribute->mutable_value(); value->set_string_value(attr.second); } // if attributes also set service.name, data takes precedence iter = data.find(OTLP_SERVICE_NAME); if (iter != data.end()) { attribute = resource->add_attributes(); attribute->set_key(OTLP_SERVICE_NAME); value = attribute->mutable_value(); value->set_string_value(iter->second); } return spans; } static void rpc_span_fill_pb_span(RPCModuleData& data, const std::unordered_map& attributes, InstrumentationLibrarySpans *spans) { Span *span = spans->add_spans(); Status *status = span->mutable_status(); KeyValue *attribute; AnyValue *attr_value; for (const auto& attr : attributes) { attribute = span->add_attributes(); attribute->set_key(attr.first); attr_value = attribute->mutable_value(); attr_value->set_string_value(attr.second); } span->set_span_id(data[SRPC_SPAN_ID].c_str(), SRPC_SPANID_SIZE); span->set_trace_id(data[SRPC_TRACE_ID].c_str(), SRPC_TRACEID_SIZE); // name is required and specified in OpenTelemetry semantic conventions. if (data.find(OTLP_METHOD_NAME) != data.end()) { span->set_name(data[OTLP_METHOD_NAME]); // for RPC attribute= span->add_attributes(); attribute->set_key(SRPC_COMPONENT_OTEL_STR); // srpc.component -> rpc.system attr_value = attribute->mutable_value(); attr_value->set_string_value(data[SRPC_COMPONENT]); } else span->set_name(data[SRPC_HTTP_METHOD]); // for HTTP // refer to : trace/semantic_conventions/http/#status int http_status_code = 0; auto iter = data.find(SRPC_HTTP_STATUS_CODE); if (iter != data.end()) http_status_code = atoi(iter->second.c_str()); for (const auto& kv : data) { const std::string& key = kv.first; const std::string& val = kv.second; if (key.compare(SRPC_PARENT_SPAN_ID) == 0) { span->set_parent_span_id(val); } else if (key.compare(SRPC_SPAN_KIND) == 0) { if (val.compare(SRPC_SPAN_KIND_CLIENT) == 0) { span->set_kind(Span_SpanKind_SPAN_KIND_CLIENT); if (http_status_code >= 400) status->set_code(Status_StatusCode_STATUS_CODE_ERROR); } else if (val.compare(SRPC_SPAN_KIND_SERVER) == 0) { span->set_kind(Span_SpanKind_SPAN_KIND_SERVER); if (http_status_code >= 500) status->set_code(Status_StatusCode_STATUS_CODE_ERROR); } } else if (key.compare(SRPC_START_TIMESTAMP) == 0) { span->set_start_time_unix_nano(atoll(data[SRPC_START_TIMESTAMP].data())); } else if (key.compare(SRPC_FINISH_TIMESTAMP) == 0) { span->set_end_time_unix_nano(atoll(data[SRPC_FINISH_TIMESTAMP].data())); } else if (key.compare(SRPC_STATE) == 0) { int state = atoi(val.c_str()); if (state == RPCStatusOK) status->set_code(Status_StatusCode_STATUS_CODE_OK); else status->set_code(Status_StatusCode_STATUS_CODE_ERROR); } else if (key.compare(WF_TASK_STATE) == 0) { int state = atoi(val.c_str()); if (state == WFT_STATE_SUCCESS) status->set_code(Status_StatusCode_STATUS_CODE_OK); else status->set_code(Status_StatusCode_STATUS_CODE_ERROR); } else if (key.compare(0, 5, "srpc.") != 0) { attribute= span->add_attributes(); attribute->set_key(key); attr_value = attribute->mutable_value(); size_t len = key.length(); if ((len > 4 && key.substr(len - 4).compare("port") == 0) || (len > 5 && key.substr(len - 5).compare("count") == 0) || (len > 6 && key.substr(len - 6).compare("length") == 0) || key.compare(SRPC_HTTP_STATUS_CODE)== 0) { attr_value->set_int_value(atoi(val.c_str())); } else { attr_value->set_string_value(val); } } } } static size_t rpc_span_log_format(RPCModuleData& data, char *str, size_t len) { const uint64_t *trace_id = (const uint64_t *)data[SRPC_TRACE_ID].c_str(); const uint64_t *span_id = (const uint64_t *)data[SRPC_SPAN_ID].c_str(); char trace_id_buf[SRPC_TRACEID_SIZE * 2 + 1]; char span_id_buf[SRPC_SPANID_SIZE * 2 + 1]; TRACE_ID_BIN_TO_HEX(trace_id, trace_id_buf); SPAN_ID_BIN_TO_HEX(span_id, span_id_buf); size_t ret = snprintf(str, len, "trace_id: %s span_id: %s", trace_id_buf, span_id_buf); for (const auto& iter : data) { if (strcmp(iter.first.c_str(), SRPC_TRACE_ID) == 0 || strcmp(iter.first.c_str(), SRPC_SPAN_ID) == 0 || strcmp(iter.first.c_str(), SRPC_FINISH_TIMESTAMP) == 0 || strcmp(iter.first.c_str(), SRPC_DURATION) == 0) { continue; } if (strcmp(iter.first.c_str(), SRPC_PARENT_SPAN_ID) == 0) { char parent_span_id_buf[SRPC_SPANID_SIZE * 2 + 1]; span_id = (const uint64_t *)iter.second.c_str(); SPAN_ID_BIN_TO_HEX(span_id, parent_span_id_buf); ret += snprintf(str + ret, len - ret, " parent_span_id: %s", parent_span_id_buf); } else if (strcmp(iter.first.c_str(), SRPC_START_TIMESTAMP) == 0) { ret += snprintf(str + ret, len - ret, " start_time: %s finish_time: %s duration: %s(ns)", data[SRPC_START_TIMESTAMP].c_str(), data[SRPC_FINISH_TIMESTAMP].c_str(), data[SRPC_DURATION].c_str()); } else if (strcmp(iter.first.c_str(), SRPC_STATE) == 0 || strcmp(iter.first.c_str(), SRPC_ERROR) == 0) { ret += snprintf(str + ret, len - ret, " %s: %s", iter.first.c_str(), iter.second.c_str()); } /* else if (strcmp(it.first.c_str(), SRPC_SPAN_LOG) == 0) { ret += snprintf(str + ret, len - ret, "\n%s trace_id: %s span_id: %s" " timestamp: %s %s", "[ANNOTATION]", trace_id_buf, span_id_buf, it.first.c_str() + strlen(SRPC_SPAN_LOG) + 1, it.second.c_str()); } */ else { const char *key = iter.first.c_str(); if (iter.first.compare(0, 5, "srpc.") == 0) key += 5; ret += snprintf(str + ret, len - ret, " %s: %s", key, iter.second.c_str()); } } return ret; } bool RPCTraceFilterPolicy::collect(RPCModuleData& span) { if (span.find(SRPC_TRACE_ID) == span.end()) return false; long long timestamp = GET_CURRENT_MS(); if (timestamp < this->last_collect_timestamp + this->stat_interval && this->spans_interval_count < this->spans_per_interval && this->spans_second_count < this->spans_per_sec) { this->spans_interval_count++; this->spans_second_count++; return true; } else if (timestamp >= this->last_collect_timestamp + this->stat_interval && this->spans_per_sec) { this->spans_interval_count = 0; if (timestamp / 1000 > this->last_collect_timestamp / 1000) // next second this->spans_second_count = 0; this->last_collect_timestamp = timestamp; if (this->spans_second_count < this->spans_per_sec) { this->spans_second_count++; this->spans_interval_count++; return true; } } return false; } bool RPCTraceFilterPolicy::report(size_t count) { long long timestamp = GET_CURRENT_MS(); if (this->last_report_timestamp == 0) this->last_report_timestamp = timestamp; if (timestamp > this->last_report_timestamp + (long long)this->report_interval || count >= this->report_threshold) { this->last_report_timestamp = timestamp; return true; } return false; } void RPCTraceLogTask::dispatch() { char str[SPAN_LOG_MAX_LENGTH]; rpc_span_log_format(this->span, str, SPAN_LOG_MAX_LENGTH); fprintf(stderr, "[SPAN_LOG] %s\n", str); this->subtask_done(); } SubTask *RPCTraceRedis::create(RPCModuleData& span) { auto iter = span.find(SRPC_TRACE_ID); if (iter == span.end()) return WFTaskFactory::create_empty_task(); auto *task = WFTaskFactory::create_redis_task(this->redis_url, this->retry_max, nullptr); protocol::RedisRequest *req = task->get_req(); char value[SPAN_LOG_MAX_LENGTH]; value[0] = '0'; rpc_span_log_format(span, value, SPAN_LOG_MAX_LENGTH); req->set_request("SET", { span[SRPC_TRACE_ID], value} ); return task; } RPCTraceOpenTelemetry::RPCTraceOpenTelemetry(const std::string& url) : RPCFilter(RPCModuleTypeTrace), url(url + OTLP_TRACES_PATH), redirect_max(OTLP_HTTP_REDIRECT_MAX), retry_max(OTLP_HTTP_RETRY_MAX), filter_policy(SPANS_PER_SECOND_DEFAULT, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT), report_status(false), report_span_count(0) { this->report_req = new TracesData; } RPCTraceOpenTelemetry::RPCTraceOpenTelemetry(const std::string& url, const std::string& path) : RPCFilter(RPCModuleTypeTrace), url(url + path), redirect_max(OTLP_HTTP_REDIRECT_MAX), retry_max(OTLP_HTTP_RETRY_MAX), filter_policy(SPANS_PER_SECOND_DEFAULT, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT), report_status(false), report_span_count(0) { this->report_req = new TracesData; } RPCTraceOpenTelemetry::RPCTraceOpenTelemetry(const std::string& url, const std::string& path, int redirect_max, int retry_max, size_t spans_per_second, size_t report_threshold, size_t report_interval) : RPCFilter(RPCModuleTypeTrace), url(url + path), redirect_max(redirect_max), retry_max(retry_max), filter_policy(spans_per_second, report_threshold, report_interval), report_status(false), report_span_count(0) { this->report_req = new TracesData; } RPCTraceOpenTelemetry::~RPCTraceOpenTelemetry() { delete this->report_req; } SubTask *RPCTraceOpenTelemetry::create(RPCModuleData& span) { std::string *output = new std::string; SubTask *next = NULL; TracesData *req = (TracesData *)this->report_req; this->mutex.lock(); if (!this->report_status) next = WFTaskFactory::create_empty_task(); else { // fprintf(stderr, "[Trace info to report]\n%s\n\n", req->DebugString().c_str()); req->SerializeToString(output); this->report_status = false; this->report_span_count = 0; req->clear_resource_spans(); this->report_map.clear(); } this->mutex.unlock(); if (next) return next; WFHttpTask *task = WFTaskFactory::create_http_task(this->url, this->redirect_max, this->retry_max, [](WFHttpTask *task) { /* protocol::HttpResponse *resp = task->get_resp(); fprintf(stderr, "[Trace report callback] state=%d error=%d\n", task->get_state(), task->get_error()); if (task->get_state() == WFT_STATE_SUCCESS) { fprintf(stderr, "%s %s %s\r\n", resp->get_http_version(), resp->get_status_code(), resp->get_reason_phrase()); } */ delete (std::string *)task->user_data; }); protocol::HttpRequest *http_req = task->get_req(); http_req->set_method(HttpMethodPost); http_req->add_header_pair("Content-Type", "application/x-protobuf"); task->user_data = output; http_req->append_output_body_nocopy(output->c_str(), output->length()); return task; } void RPCTraceOpenTelemetry::add_attributes(const std::string& key, const std::string& value) { this->mutex.lock(); this->attributes.insert(std::make_pair(key, value)); this->mutex.unlock(); } void RPCTraceOpenTelemetry::add_span_attributes(const std::string& key, const std::string& value) { this->mutex.lock(); this->span_attributes.insert(std::make_pair(key, value)); this->mutex.unlock(); } size_t RPCTraceOpenTelemetry::clear_attributes() { size_t ret; this->mutex.lock(); ret = this->attributes.size(); this->attributes.clear(); this->mutex.unlock(); return ret; } size_t RPCTraceOpenTelemetry::clear_span_attributes() { size_t ret; this->mutex.lock(); ret = this->span_attributes.size(); this->span_attributes.clear(); this->mutex.unlock(); return ret; } bool RPCTraceOpenTelemetry::filter(RPCModuleData& data) { std::unordered_map::iterator it; InstrumentationLibrarySpans *spans; std::string service_name; bool ret; auto iter = data.find(OTLP_SERVICE_NAME); if (iter != data.end()) { service_name = iter->second; } else // for HTTP { service_name = data[SRPC_COMPONENT] + std::string(".") + data[SRPC_HTTP_SCHEME]; if (data.find(SRPC_SPAN_KIND_CLIENT) != data.end()) service_name += ".client"; else service_name += ".server"; } this->mutex.lock(); if (this->filter_policy.collect(data)) { ++this->report_span_count; it = this->report_map.find(service_name); if (it == this->report_map.end()) { spans = rpc_span_fill_pb_request(data, this->attributes, (TracesData *)this->report_req); this->report_map.insert({service_name, spans}); } else spans = (InstrumentationLibrarySpans *)it->second; rpc_span_fill_pb_span(data, this->span_attributes, spans); } ret = this->filter_policy.report(this->report_span_count); if (ret) this->report_status = true; this->mutex.unlock(); return ret; } } // end namespace srpc sogou-srpc-ac65014/src/module/rpc_trace_filter.h000066400000000000000000000153411525217424400217060ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_TRACE_FILTER_H__ #define __RPC_TRACE_FILTER_H__ #include #include #include #include #include #include #include #include #include "workflow/WFTask.h" #include "workflow/WFTaskFactory.h" #include "workflow/RedisMessage.h" #include "rpc_basic.h" #include "rpc_trace_module.h" namespace srpc { static constexpr unsigned int SPAN_LIMIT_DEFAULT = 1; static constexpr size_t SPAN_LOG_MAX_LENGTH = 1024; static constexpr size_t UINT64_STRING_LENGTH = 20; static constexpr unsigned int SPAN_REDIS_RETRY_MAX = 0; static constexpr const char *SPAN_BATCH_LOG_NAME_DEFAULT = "./trace_info.log"; static constexpr size_t SPAN_BATCH_LOG_SIZE_DEFAULT = 4 * 1024 * 1024; static constexpr unsigned int SPANS_PER_SECOND_DEFAULT = 1000; static constexpr const char *OTLP_TRACES_PATH = "/v1/traces"; class RPCTraceFilterPolicy { public: RPCTraceFilterPolicy(size_t spans_per_second, size_t report_threshold, size_t report_interval_msec) : stat_interval(1), // default 1 msec spans_per_sec(spans_per_second), last_collect_timestamp(0), spans_second_count(0), spans_interval_count(0), report_threshold(report_threshold), report_interval(report_interval_msec), last_report_timestamp(0) { this->spans_per_interval = (this->spans_per_sec + 999) / 1000; } void set_spans_per_sec(size_t n) { this->spans_per_sec = n; this->spans_per_interval = (n * this->stat_interval + 999 ) / 1000; } void set_stat_interval(int msec) { if (msec <= 0) msec = 1; this->stat_interval = msec; this->spans_per_interval = (this->spans_per_sec * msec + 999) / 1000; } void set_report_threshold(size_t threshold) { if (threshold <= 0) threshold = 1; this->report_threshold = threshold; } void set_report_interval(int msec) { if (msec <= 0) msec = 1; this->report_interval = msec; } bool collect(RPCModuleData& span); bool report(size_t count); private: int stat_interval; size_t spans_per_sec; size_t spans_per_interval; long long last_collect_timestamp; std::atomic spans_second_count; std::atomic spans_interval_count; size_t report_threshold; // spans to report at most size_t report_interval; long long last_report_timestamp; }; class RPCTraceLogTask : public WFGenericTask { public: RPCTraceLogTask(RPCModuleData& span, std::function callback) : span(span), callback(std::move(callback)) {} private: virtual void dispatch(); virtual SubTask *done() { SeriesWork *series = series_of(this); if (this->callback) this->callback(this); delete this; return series->pop(); } public: RPCModuleData span; std::function callback; }; class RPCTraceDefault : public RPCFilter { public: RPCTraceDefault() : RPCFilter(RPCModuleTypeTrace), filter_policy(SPANS_PER_SECOND_DEFAULT, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT) {} RPCTraceDefault(size_t spans_per_second) : RPCFilter(RPCModuleTypeTrace), filter_policy(spans_per_second, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT) {} private: SubTask *create(RPCModuleData& span) override { return new RPCTraceLogTask(span, nullptr); } bool filter(RPCModuleData& span) override { return this->filter_policy.collect(span); } public: void set_spans_per_sec(size_t n) { this->filter_policy.set_spans_per_sec(n); } void set_stat_interval(int msec) { this->filter_policy.set_stat_interval(msec); } private: RPCTraceFilterPolicy filter_policy; }; class RPCTraceRedis : public RPCFilter { public: RPCTraceRedis(const std::string& url) : RPCFilter(RPCModuleTypeTrace), retry_max(SPAN_REDIS_RETRY_MAX), filter_policy(SPANS_PER_SECOND_DEFAULT, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT) {} RPCTraceRedis(const std::string& url, int retry_max, size_t spans_per_second) : RPCFilter(RPCModuleTypeTrace), redis_url(url), retry_max(retry_max), filter_policy(spans_per_second, RPC_REPORT_THREHOLD_DEFAULT, RPC_REPORT_INTERVAL_DEFAULT) {} private: std::string redis_url; int retry_max; private: SubTask *create(RPCModuleData& span) override; bool filter(RPCModuleData& span) override { return this->filter_policy.collect(span); } public: void set_spans_per_sec(size_t n) { this->filter_policy.set_spans_per_sec(n); } void set_stat_interval(int msec) { this->filter_policy.set_stat_interval(msec); } private: RPCTraceFilterPolicy filter_policy; }; class RPCTraceOpenTelemetry : public RPCFilter { public: void set_spans_per_sec(size_t n) { this->filter_policy.set_spans_per_sec(n); } void set_stat_interval(int msec) { this->filter_policy.set_stat_interval(msec); } void set_report_threshold(size_t threshold) { this->filter_policy.set_report_threshold(threshold); } void set_report_interval(int msec) { this->filter_policy.set_report_interval(msec); } // attributes for client level, such as ProviderID void add_attributes(const std::string& key, const std::string& value); size_t clear_attributes(); // attributes for each span void add_span_attributes(const std::string& key, const std::string& value); size_t clear_span_attributes(); private: std::string url; int redirect_max; int retry_max; RPCTraceFilterPolicy filter_policy; google::protobuf::Message *report_req; std::unordered_map report_map; bool report_status; size_t report_span_count; protected: std::mutex mutex; std::unordered_map attributes; std::unordered_map span_attributes; private: SubTask *create(RPCModuleData& span) override; bool filter(RPCModuleData& span) override; public: RPCTraceOpenTelemetry(const std::string& url); RPCTraceOpenTelemetry(const std::string& url, const std::string& path); RPCTraceOpenTelemetry(const std::string& url, const std::string& path, int redirect_max, int retry_max, size_t spans_per_second, size_t report_threshold, size_t report_interval); virtual ~RPCTraceOpenTelemetry(); }; } // end namespace srpc #endif sogou-srpc-ac65014/src/module/rpc_trace_module.cc000066400000000000000000000113731525217424400220450ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include "rpc_trace_module.h" #include "workflow/HttpUtil.h" namespace srpc { static void generate_common_trace(SubTask *task, RPCModuleData& data) { // Don't set start_timestamp here, which may mislead other modules in end() if (data.find(SRPC_TRACE_ID) == data.end()) { uint64_t trace_id_high = SRPCGlobal::get_instance()->get_random(); uint64_t trace_id_low = SRPCGlobal::get_instance()->get_random(); std::string trace_id_buf(SRPC_TRACEID_SIZE + 1, 0); memcpy((char *)trace_id_buf.c_str(), &trace_id_high, SRPC_TRACEID_SIZE / 2); memcpy((char *)trace_id_buf.c_str() + SRPC_TRACEID_SIZE / 2, &trace_id_low, SRPC_TRACEID_SIZE / 2); data[SRPC_TRACE_ID] = std::move(trace_id_buf); } else data[SRPC_PARENT_SPAN_ID] = data[SRPC_SPAN_ID]; uint64_t span_id = SRPCGlobal::get_instance()->get_random(); std::string span_id_buf(SRPC_SPANID_SIZE + 1, 0); memcpy((char *)span_id_buf.c_str(), &span_id, SRPC_SPANID_SIZE); data[SRPC_SPAN_ID] = std::move(span_id_buf); } bool TraceModule::client_begin(SubTask *task, RPCModuleData& data) { generate_common_trace(task, data); data[SRPC_START_TIMESTAMP] = std::to_string(GET_CURRENT_NS()); data[SRPC_SPAN_KIND] = SRPC_SPAN_KIND_CLIENT; // clear other unnecessary module_data since the data comes from series data.erase(SRPC_DURATION); data.erase(SRPC_FINISH_TIMESTAMP); return true; } bool TraceModule::client_end(SubTask *task, RPCModuleData& data) { // 1. failed to call any client_begin() previously if (data.find(SRPC_START_TIMESTAMP) == data.end()) { generate_common_trace(task, data); } // 2. other modules has not set duration yet else if (data.find(SRPC_DURATION) == data.end()) { unsigned long long end_time = GET_CURRENT_NS(); data[SRPC_FINISH_TIMESTAMP] = std::to_string(end_time); data[SRPC_DURATION] = std::to_string(end_time - atoll(data[SRPC_START_TIMESTAMP].data())); } return true; } bool TraceModule::server_begin(SubTask *task, RPCModuleData& data) { generate_common_trace(task, data); data[SRPC_START_TIMESTAMP] = std::to_string(GET_CURRENT_NS()); data[SRPC_SPAN_KIND] = SRPC_SPAN_KIND_SERVER; return true; } bool TraceModule::server_end(SubTask *task, RPCModuleData& data) { // 1. failed to call any server_begin() previously if (data.find(SRPC_START_TIMESTAMP) == data.end()) { generate_common_trace(task, data); } // 2. some other module has not set duration yet else if (data.find(SRPC_DURATION) == data.end()) { unsigned long long end_time = GET_CURRENT_NS(); data[SRPC_FINISH_TIMESTAMP] = std::to_string(end_time); data[SRPC_DURATION] = std::to_string(end_time - atoll(data[SRPC_START_TIMESTAMP].data())); } return true; } void TraceModule::client_begin_request(protocol::HttpRequest *req, RPCModuleData& data) const { data[SRPC_HTTP_METHOD] = req->get_method(); const void *body; size_t body_len; req->get_parsed_body(&body, &body_len); data[SRPC_HTTP_REQ_LEN] = std::to_string(body_len); } void TraceModule::client_end_response(protocol::HttpResponse *resp, RPCModuleData& data) const { data[SRPC_HTTP_STATUS_CODE] = resp->get_status_code(); const void *body; size_t body_len; resp->get_parsed_body(&body, &body_len); data[SRPC_HTTP_RESP_LEN] = std::to_string(body_len); } void TraceModule::server_begin_request(protocol::HttpRequest *req, RPCModuleData& data) const { data[SRPC_HTTP_METHOD] = req->get_method(); data[SRPC_HTTP_TARGET] = req->get_request_uri(); const void *body; size_t body_len; req->get_parsed_body(&body, &body_len); data[SRPC_HTTP_REQ_LEN] = std::to_string(body_len); std::string name; std::string value; protocol::HttpHeaderCursor req_cursor(req); int flag = 0; while (req_cursor.next(name, value) && flag != 3) { if (strcasecmp(name.data(), "Host") == 0) { data[SRPC_HTTP_HOST_NAME] = value; flag |= 1; } else if (strcasecmp(name.data(), "X-Forwarded-For") == 0) { data[SRPC_HTTP_CLIENT_IP] = value; flag |= (1 << 1); } } } void TraceModule::server_end_response(protocol::HttpResponse *resp, RPCModuleData& data) const { data[SRPC_HTTP_STATUS_CODE] = resp->get_status_code(); data[SRPC_HTTP_RESP_LEN] = std::to_string(resp->get_output_body_size()); } } // end namespace srpc sogou-srpc-ac65014/src/module/rpc_trace_module.h000066400000000000000000000200451525217424400217030ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_TRACE_MODULE_H__ #define __RPC_TRACE_MODULE_H__ #include #include #include #include #include #include "workflow/WFTask.h" #include "rpc_basic.h" #include "rpc_module.h" #include "rpc_context.h" #ifdef _WIN32 #include #else #include #include #endif namespace srpc { // follows the basic conventions of RPC part in opentracing static constexpr char const *SRPC_COMPONENT = "srpc.component"; // span tags static constexpr char const *SRPC_SPAN_KIND = "srpc.span.kind"; static constexpr char const *SRPC_SPAN_KIND_CLIENT = "srpc.client"; static constexpr char const *SRPC_SPAN_KIND_SERVER = "srpc.server"; static constexpr char const *SRPC_STATE = "rpc.state"; static constexpr char const *SRPC_ERROR = "rpc.error"; static constexpr char const *WF_TASK_STATE = "task.state"; static constexpr char const *WF_TASK_ERROR = "task.error"; static constexpr char const *SRPC_REMOTE_IP = "server.address"; static constexpr char const *SRPC_REMOTE_PORT = "server.port"; static constexpr char const *SRPC_SAMPLING_PRIO = "srpc.sampling.priority"; // for ext tags static constexpr char const *SRPC_DATA_TYPE = "srpc.data.type"; static constexpr char const *SRPC_COMPRESS_TYPE = "srpc.compress.type"; // for http static constexpr char const *SRPC_HTTP_SOCK_FAMILY = "net.sock.family"; static constexpr char const *SRPC_HTTP_SOCK_ADDR = "net.sock.peer.addr"; static constexpr char const *SRPC_HTTP_SOCK_PORT = "net.sock.peer.port"; static constexpr char const *SRPC_HTTP_REQ_LEN = "http.request_content_length"; static constexpr char const *SRPC_HTTP_RESP_LEN = "http.response_content_length"; // for http client static constexpr char const *SRPC_HTTP_CLIENT_URL = "http.url"; static constexpr char const *SRPC_HTTP_PEER_NAME = "net.peer.name"; static constexpr char const *SRPC_HTTP_PEER_PORT = "net.peer.port"; static constexpr char const *SRPC_HTTP_RESEND_COUNT = "http.resend_count"; // for http server static constexpr char const *SRPC_HTTP_SCHEME = "http.scheme"; static constexpr char const *SRPC_HTTP_HOST_NAME = "net.host.name"; static constexpr char const *SRPC_HTTP_HOST_PORT = "net.host.port"; // /users/12314/?q=ddds static constexpr char const *SRPC_HTTP_TARGET = "http.target"; // The IP address of the original client behind all proxies, from X-Forwarded-For static constexpr char const *SRPC_HTTP_CLIENT_IP = "http.client_ip"; // Basic TraceModule for generating general span data. // Each kind of network task can derived its own TraceModule. class TraceModule : public RPCModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool client_end(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; protected: void client_begin_request(protocol::HttpRequest *req, RPCModuleData& data) const; void client_end_response(protocol::HttpResponse *resp, RPCModuleData& data) const; void server_begin_request(protocol::HttpRequest *req, RPCModuleData& data) const; void server_end_response(protocol::HttpResponse *resp, RPCModuleData& data) const; void client_begin_request(protocol::ProtocolMessage *req, RPCModuleData& data) const { } void client_end_response(protocol::ProtocolMessage *resp, RPCModuleData& data) const { } void server_begin_request(protocol::ProtocolMessage *req, RPCModuleData& data) const { } void server_end_response(protocol::ProtocolMessage *resp, RPCModuleData& data) const { } public: TraceModule() : RPCModule(RPCModuleTypeTrace) { } }; // Fill RPC related data in trace module template class RPCTraceModule : public TraceModule { public: bool client_begin(SubTask *task, RPCModuleData& data) override; bool client_end(SubTask *task, RPCModuleData& data) override; bool server_begin(SubTask *task, RPCModuleData& data) override; bool server_end(SubTask *task, RPCModuleData& data) override; private: void client_basic_info(CLIENT_TASK *task, RPCModuleData& data); void server_basic_info(SERVER_TASK *task, RPCModuleData& data); }; ////////// impl template void RPCTraceModule::client_basic_info(CTASK *task, RPCModuleData& data) { auto *req = task->get_req(); data[SRPC_COMPONENT] = SRPC_COMPONENT_SRPC; data[OTLP_SERVICE_NAME] = req->get_service_name(); data[OTLP_METHOD_NAME] = req->get_method_name(); data[SRPC_DATA_TYPE] = std::to_string(req->get_data_type()); data[SRPC_COMPRESS_TYPE] = std::to_string(req->get_compress_type()); } template bool RPCTraceModule::client_begin(SubTask *task, RPCModuleData& data) { TraceModule::client_begin(task, data); auto *client_task = static_cast(task); this->client_basic_info(client_task, data); auto *req = client_task->get_req(); TraceModule::client_begin_request(req, data); return true; } template bool RPCTraceModule::client_end(SubTask *task, RPCModuleData& data) { TraceModule::client_end(task, data); std::string ip; unsigned short port; auto *client_task = static_cast(task); auto *resp = client_task->get_resp(); // failed to call client_begin if (data.find(SRPC_COMPONENT) == data.end()) this->client_basic_info(client_task, data); data[SRPC_STATE] = std::to_string(resp->get_status_code()); if (resp->get_status_code() != RPCStatusOK) { data[SRPC_ERROR] = std::to_string(resp->get_error()); return true; } if (client_task->get_remote(ip, &port)) { data[SRPC_REMOTE_IP] = std::move(ip); data[SRPC_REMOTE_PORT] = std::to_string(port); } TraceModule::client_end_response(resp, data); return true; } template void RPCTraceModule::server_basic_info(STASK *task, RPCModuleData& data) { std::string ip; unsigned short port; auto *req = task->get_req(); data[SRPC_COMPONENT] = SRPC_COMPONENT_SRPC; data[OTLP_SERVICE_NAME] = req->get_service_name(); data[OTLP_METHOD_NAME] = "/" + req->get_service_name() + "/" + req->get_method_name(); data[SRPC_DATA_TYPE] = std::to_string(req->get_data_type()); data[SRPC_COMPRESS_TYPE] = std::to_string(req->get_compress_type()); if (task->get_remote(ip, &port)) { data[SRPC_REMOTE_IP] = std::move(ip); data[SRPC_REMOTE_PORT] = std::to_string(port); } } template bool RPCTraceModule::server_begin(SubTask *task, RPCModuleData& data) { TraceModule::server_begin(task, data); auto *server_task = static_cast(task); this->server_basic_info(server_task, data); auto *req = server_task->get_req(); TraceModule::server_begin_request(req, data); return true; } template bool RPCTraceModule::server_end(SubTask *task, RPCModuleData& data) { TraceModule::server_end(task, data); auto *server_task = static_cast(task); auto *resp = server_task->get_resp(); // failed to call server_begin if (data.find(SRPC_COMPONENT) == data.end()) this->server_basic_info(server_task, data); data[SRPC_STATE] = std::to_string(resp->get_status_code()); if (resp->get_status_code() != RPCStatusOK) { data[SRPC_ERROR] = std::to_string(resp->get_error()); return true; } TraceModule::server_end_response(resp, data); return true; } } // end namespace srpc #endif sogou-srpc-ac65014/src/rpc_basic.cc000066400000000000000000000034251525217424400171750ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifdef _WIN32 #include #else #include #include #endif #include "rpc_basic.h" #include "rpc_module.h" namespace srpc { void RPCCommon::log_format(std::string& key, std::string& value, const RPCLogVector& fields) { if (fields.size() == 0) return; char buffer[100]; snprintf(buffer, 100, "%s%c%lld", SRPC_SPAN_LOG, ' ', GET_CURRENT_MS()); key = std::move(buffer); value = "{\""; for (auto& field : fields) { value = value + std::move(field.first) + "\":\"" + std::move(field.second) + "\","; } value[value.length() - 1] = '}'; } bool RPCCommon::addr_to_string(const struct sockaddr *addr, char *ip_str, socklen_t len, unsigned short *port) { const char *ret = NULL; if (addr->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)addr; ret = inet_ntop(AF_INET, &sin->sin_addr, ip_str, len); *port = ntohs(sin->sin_port); } else if (addr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr; ret = inet_ntop(AF_INET6, &sin6->sin6_addr, ip_str, len); *port = ntohs(sin6->sin6_port); } else errno = EINVAL; return ret != NULL; } } // namespace srpc sogou-srpc-ac65014/src/rpc_basic.h000066400000000000000000000126021525217424400170340ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_BASIC_H__ #define __RPC_BASIC_H__ #include #include #include #include "rpc_buffer.h" #include #ifdef _WIN32 #include #else #include #include #endif namespace srpc { static constexpr const char *SRPC_SCHEME = "srpc"; static constexpr unsigned short SRPC_DEFAULT_PORT = 1412; static constexpr const char *SRPC_SSL_SCHEME = "srpcs"; static constexpr unsigned short SRPC_SSL_DEFAULT_PORT = 6462; static constexpr size_t RPC_BODY_SIZE_LIMIT = 2LL * 1024 * 1024 * 1024; static constexpr int SRPC_MODULE_MAX = 5; static constexpr size_t SRPC_SPANID_SIZE = 8; static constexpr size_t SRPC_TRACEID_SIZE = 16; #ifndef htonll #if __BYTE_ORDER == __LITTLE_ENDIAN static inline uint64_t htonll(uint64_t x) { return ((uint64_t)htonl(x & 0xFFFFFFFF) << 32) + htonl(x >> 32); } static inline uint64_t ntohll(uint64_t x) { return ((uint64_t)ntohl(x & 0xFFFFFFFF) << 32) + ntohl(x >> 32); } #elif __BYTE_ORDER == __BIG_ENDIAN static inline uint64_t htonll(uint64_t x) { return x; } static inline uint64_t ntohll(uint64_t x) { return x; } #else # error "unknown byte order" #endif #endif #define SRPC_JSON_OPTION_ADD_WHITESPACE (1<<3) #define SRPC_JSON_OPTION_ENUM_AS_INITS (1<<4) #define SRPC_JSON_OPTION_PRESERVE_NAMES (1<<5) #define SRPC_JSON_OPTION_FIELDS_NO_PRECENCE (1<<6) using ProtobufIDLMessage = google::protobuf::Message; using RPCLogVector = std::vector>; enum RPCDataType { RPCDataUndefined = -1, RPCDataProtobuf = 0, RPCDataThrift = 1, RPCDataJson = 2, }; enum RPCStatusCode { RPCStatusUndefined = 0, RPCStatusOK = 1, RPCStatusServiceNotFound = 2, RPCStatusMethodNotFound = 3, RPCStatusMetaError = 4, RPCStatusReqCompressSizeInvalid = 5, RPCStatusReqDecompressSizeInvalid = 6, RPCStatusReqCompressNotSupported = 7, RPCStatusReqDecompressNotSupported = 8, RPCStatusReqCompressError = 9, RPCStatusReqDecompressError = 10, RPCStatusReqSerializeError = 11, RPCStatusReqDeserializeError = 12, RPCStatusRespCompressSizeInvalid = 13, RPCStatusRespDecompressSizeInvalid = 14, RPCStatusRespCompressNotSupported = 15, RPCStatusRespDecompressNotSupported = 16, RPCStatusRespCompressError = 17, RPCStatusRespDecompressError = 18, RPCStatusRespSerializeError = 19, RPCStatusRespDeserializeError = 20, RPCStatusIDLSerializeNotSupported = 21, RPCStatusIDLDeserializeNotSupported = 22, RPCStatusModuleFilterFailed = 23, RPCStatusURIInvalid = 30, RPCStatusUpstreamFailed = 31, RPCStatusSystemError = 100, RPCStatusSSLError = 101, RPCStatusDNSError = 102, RPCStatusProcessTerminated = 103, }; enum RPCCompressType { RPCCompressNone = 0, RPCCompressSnappy = 1, RPCCompressGzip = 2, RPCCompressZlib = 3, RPCCompressLz4 = 4, RPCCompressMax = 5, }; enum RPCModuleType { RPCModuleTypeEmpty = 0, RPCModuleTypeTrace = 1, RPCModuleTypeMetrics = 2, RPCModuleTypeLog = 3, RPCModuleTypeCustom = 4, }; class RPCCommon { public: static void log_format(std::string& key, std::string& value, const RPCLogVector& fields); static bool addr_to_string(const struct sockaddr *addr, char *ip_str, socklen_t len, unsigned short *port); }; static inline long long GET_CURRENT_MS() { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); }; static inline long long GET_CURRENT_MS_STEADY() { return std::chrono::duration_cast( std::chrono::steady_clock::now().time_since_epoch()).count(); } static inline unsigned long long GET_CURRENT_NS() { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); } static inline void TRACE_ID_BIN_TO_HEX(const uint64_t trace_id[2], char hex[SRPC_TRACEID_SIZE * 2 + 1]) { snprintf(hex, SRPC_TRACEID_SIZE * 2 + 1, "%016llx%016llx", (unsigned long long)ntohll(trace_id[0]), (unsigned long long)ntohll(trace_id[1])); } static inline void SPAN_ID_BIN_TO_HEX(const uint64_t span_id[1], char hex[SRPC_SPANID_SIZE * 2 + 1]) { snprintf(hex, SRPC_SPANID_SIZE * 2 + 1, "%016llx", (unsigned long long)ntohll(span_id[0])); } static inline void TRACE_ID_HEX_TO_BIN(const char hex[SRPC_TRACEID_SIZE * 2 + 1], uint64_t trace_id[2]) { sscanf(hex, "%016llx%016llx", (unsigned long long *)&trace_id[0], (unsigned long long *)&trace_id[1]); trace_id[0] = ntohll(trace_id[0]); trace_id[1] = ntohll(trace_id[1]); } static inline void SPAN_ID_HEX_TO_BIN(const char hex[SRPC_SPANID_SIZE * 2 + 1], uint64_t span_id[1]) { sscanf(hex, "%016llx", (unsigned long long *)&span_id[0]); span_id[0] = ntohll(span_id[0]); } } // end namespace srpc #endif sogou-srpc-ac65014/src/rpc_buffer.cc000066400000000000000000000222471525217424400173700ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include "rpc_buffer.h" namespace srpc { void RPCBuffer::clear_list_buffer() { for (const auto& ele: buffer_list_) { if (!ele.is_nocopy) { if (ele.is_new) delete []((char *)ele.buf); else free(ele.buf); } } } void RPCBuffer::clear() { clear_list_buffer(); buffer_list_.clear(); size_ = 0; piece_min_size_ = BUFFER_PIECE_MIN_SIZE; piece_max_size_ = BUFFER_PIECE_MAX_SIZE; init_read_over_ = false; last_piece_left_ = 0; } bool RPCBuffer::append(void *buf, size_t buflen, int mode) { if (mode == BUFFER_MODE_COPY) return write(buf, buflen); buffer_t ele; void *left_buf; if (last_piece_left_ > 0) { const auto it = buffer_list_.rbegin(); left_buf = (char *)it->buf + it->buflen; } ele.buflen = buflen; ele.buf = buf; ele.is_nocopy = (mode == BUFFER_MODE_NOCOPY); ele.is_new = (mode == BUFFER_MODE_GIFT_NEW); buffer_list_.emplace_back(std::move(ele)); size_ += buflen; if (last_piece_left_ > 0) { ele.buflen = 0; ele.buf = left_buf; ele.is_nocopy = true; ele.is_new = false; buffer_list_.emplace_back(std::move(ele)); } return true; } bool RPCBuffer::append(const void *buf, size_t buflen, int mode) { if (mode == BUFFER_MODE_COPY) return write(buf, buflen); return append(const_cast(buf), buflen, mode); } size_t RPCBuffer::backup(size_t count) { if (count == 0 || buffer_list_.empty()) return 0; const auto it = buffer_list_.rbegin(); size_t sz = 0; if (it->buflen > count) { sz = count; it->buflen -= count; } else { sz = it->buflen; it->buflen = 0; } last_piece_left_ += sz; size_ -= sz; return sz; } bool RPCBuffer::read(void *buf, size_t buflen) { while (buflen > 0) { const void *p; size_t sz = buflen; if (!fetch(&p, &sz)) return false; memcpy(buf, p, sz); buf = (char *)buf + sz; buflen -= sz; } return true; } bool RPCBuffer::write(const void *buf, size_t buflen) { while (buflen > 0) { void *p; size_t sz = buflen; if (!acquire(&p, &sz)) return false; memcpy(p, buf, sz); buf = (const char *)buf + sz; buflen -= sz; } return true; } void RPCBuffer::rewind() { cur_.first = buffer_list_.begin(); cur_.second = 0; init_read_over_ = true; } long RPCBuffer::seek(long offset) { if (offset > 0) return read_skip(offset); else if (offset < 0) return read_back(offset); return 0; } size_t RPCBuffer::peek(const void **buf) { return internal_fetch(buf, BUFFER_FETCH_STAY); } size_t RPCBuffer::fetch(const void **buf) { return internal_fetch(buf, BUFFER_FETCH_MOVE); } RPCBuffer::~RPCBuffer() { clear_list_buffer(); } size_t RPCBuffer::acquire(void **buf) { if (last_piece_left_ > 0) { const auto it = buffer_list_.rbegin(); size_t sz = last_piece_left_; *buf = (char *)it->buf + it->buflen; it->buflen += last_piece_left_; size_ += last_piece_left_; last_piece_left_ = 0; return sz; } *buf = malloc(piece_min_size_); if (*buf == NULL) return 0; append(*buf, piece_min_size_, BUFFER_MODE_GIFT_MALLOC); return piece_min_size_; } bool RPCBuffer::acquire(void **buf, size_t *size) { if (last_piece_left_ == 0) { size_t sz = *size; if (sz > piece_max_size_) sz = piece_max_size_; if (sz < piece_min_size_) sz = piece_min_size_; *buf = malloc(sz); if (*buf == NULL) { *size = 0; return false; } append(*buf, 0, BUFFER_MODE_GIFT_MALLOC); last_piece_left_ = sz; } const auto it = buffer_list_.rbegin(); *buf = (char *)it->buf + it->buflen; if (last_piece_left_ <= *size) { *size = last_piece_left_; last_piece_left_ = 0; } else last_piece_left_ -= *size; it->buflen += *size; size_ += *size; return true; } int RPCBuffer::merge_all(struct iovec& iov) { size_t sz = 0; void *new_base = malloc(size_); if (!new_base) return -1; for (const auto& ele : buffer_list_) { memcpy((char *)new_base + sz, ele.buf, ele.buflen); sz += ele.buflen; if (!ele.is_nocopy) { if (ele.is_new) delete []((char *)ele.buf); else free(ele.buf); } } iov.iov_base = new_base; iov.iov_len = sz; buffer_list_.resize(1); auto head = buffer_list_.begin(); head->buf = new_base; head->buflen = sz; head->is_nocopy = false; head->is_new = false; return 1; } int RPCBuffer::encode(struct iovec *iov, int count) { if (count <= 0) { errno = EINVAL; return -1; } if (count == 1) return merge_all(iov[0]) == 0 ? 1 : -1; //if (buffer_list_.size() > count) //{ // // todo try merge all Adjacent copyed // // nocopy copy nocopy copy copy nocopy ... // // 0 1 0 1 1 0 ... // // merge same, test if <= count //} while (buffer_list_.size() > (size_t)count) { //merge half auto next = buffer_list_.begin(); auto cur = next; ++next; while (next != buffer_list_.end()) { size_t sz = cur->buflen + next->buflen; void *new_base = malloc(sz); if (!new_base) return -1; memcpy(new_base, cur->buf, cur->buflen); memcpy((char *)new_base + cur->buflen, next->buf, next->buflen); if (!cur->is_nocopy) { if (cur->is_new) delete []((char *)cur->buf); else free(cur->buf); } if (!next->is_nocopy) { if (next->is_new) delete []((char *)next->buf); else free(next->buf); } cur->buf = new_base; cur->buflen = sz; cur->is_nocopy = false; cur->is_new = false; buffer_list_.erase(next); ++cur; next = cur; ++next; } } int n = 0; for (const auto& ele : buffer_list_) { if (ele.buflen > 0) { iov[n].iov_base = ele.buf; iov[n].iov_len = ele.buflen; n++; } } return n; } bool RPCBuffer::fetch(const void **buf, size_t *size) { if (!init_read_over_) rewind(); while (cur_.first != buffer_list_.end() && cur_.second >= cur_.first->buflen) { ++cur_.first; cur_.second = 0; } if (cur_.first != buffer_list_.end()) { size_t n = cur_.first->buflen - cur_.second; *buf = (char *)cur_.first->buf + cur_.second; if (*size < n) cur_.second += *size; else { *size = n; ++cur_.first; cur_.second = 0; } return true; } *buf = nullptr; *size = 0; return false; } size_t RPCBuffer::internal_fetch(const void **buf, bool move_or_stay) { size_t sz = 0; if (!init_read_over_) rewind(); while (cur_.first != buffer_list_.end() && cur_.second >= cur_.first->buflen) { ++cur_.first; cur_.second = 0; } if (cur_.first == buffer_list_.end()) *buf = nullptr; else { *buf = (char *)cur_.first->buf + cur_.second; sz = cur_.first->buflen - cur_.second; if (move_or_stay) { ++cur_.first; cur_.second = 0; } } return sz; } long RPCBuffer::read_skip(long offset) { long origin = offset; if (!init_read_over_) rewind(); while (offset > 0 && cur_.first != buffer_list_.end()) { if (cur_.second < cur_.first->buflen) { long n = cur_.first->buflen - cur_.second; if (offset < n) { cur_.second += offset; offset = 0; break; } offset -= n; } ++cur_.first; cur_.second = 0; } return origin - offset; } long RPCBuffer::read_back(long offset) { long origin = offset; if (!init_read_over_) rewind(); if (cur_.first == buffer_list_.end()) { if (buffer_list_.empty()) return 0; else { --cur_.first; cur_.second = cur_.first->buflen; } } while (offset < 0) { if (cur_.second > 0) { long n = cur_.second; if (n + offset >= 0) { cur_.second += offset; offset = 0; break; } offset += n; } if (cur_.first == buffer_list_.begin()) { cur_.second = 0; break; } --cur_.first; cur_.second = cur_.first->buflen; } return origin - offset; } size_t RPCBuffer::cut(size_t offset, RPCBuffer *out) { rewind(); size_ = seek(offset); size_t cutsize = 0; const void *buf; size_t sz = peek(&buf); // sz is the length in this current buf, not final len if (sz > 0) { out->last_piece_left_ = 0; // drop auto erase_it = buffer_list_.end(); while (cur_.first != buffer_list_.end()) { auto it = cur_.first; size_t cur_len = it->buflen - cur_.second; if (it->is_nocopy || cur_.second != 0) { out->append((char *)it->buf + cur_.second, cur_len, BUFFER_MODE_NOCOPY); } else { out->append((char *)it->buf + cur_.second, cur_len, it->is_new ? BUFFER_MODE_GIFT_NEW : BUFFER_MODE_GIFT_MALLOC); } if (erase_it == buffer_list_.end() && cur_.second == 0) erase_it = it; else cur_.first->buflen = cur_.second; cutsize += cur_len; ++cur_.first; cur_.second = 0; } if (last_piece_left_ > 0) out->last_piece_left_ = last_piece_left_; if (erase_it != buffer_list_.end()) buffer_list_.erase(erase_it, buffer_list_.end()); } rewind(); return cutsize; } } // namespace srpc sogou-srpc-ac65014/src/rpc_buffer.h000066400000000000000000000154051525217424400172300ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_BUFFER_H__ #define __RPC_BUFFER_H__ #ifdef _WIN32 #include #else #include #endif #include #include #include namespace srpc { static constexpr int BUFFER_PIECE_MIN_SIZE = 2 * 1024; static constexpr int BUFFER_PIECE_MAX_SIZE = 256 * 1024; static constexpr int BUFFER_MODE_COPY = 0; static constexpr int BUFFER_MODE_NOCOPY = 1; static constexpr int BUFFER_MODE_GIFT_NEW = 2; static constexpr int BUFFER_MODE_GIFT_MALLOC = 3; static constexpr bool BUFFER_FETCH_MOVE = true; static constexpr bool BUFFER_FETCH_STAY = false; /** * @brief Buffer Class * @details * - Thread Safety : NO * - All buffer should allocated by new char[...] or malloc * - Gather buffer piece by piece * - Get buffer one by one */ class RPCBuffer { public: /** * @brief Free all buffer and rewind every state to initialize. * @note NEVER fail */ void clear(); /** * @brief Get all size of buffer */ size_t size() const { return size_; } /** * @brief Cut current buffer at absolutely offset. Current buffer keeps * the first part and gives the second part to the out buffer. * @param[in] offset where to cut * @param[in] out points to out buffer * @return actual give how many bytes to out * @note this will cause current buffer rewind() */ size_t cut(size_t offset, RPCBuffer *out); public: /** * @brief For write. Add one buffer allocated by RPCBuffer * @param[in,out] buf a pointer to a buffer * @param[in,out] size points to the expect size of buffer, return actual size * @return false if OOM, or your will get a buffer actual size <=expect-size * @note Ownership of this buffer remains with the stream */ bool acquire(void **buf, size_t *size); /** * @brief For write. Add one buffer allocated by RPCBuffer * @param[in,out] buf a pointer to a buffer * @return 0 if OOM, or the actual size of the buffer * @note Ownership of this buffer remains with the stream */ size_t acquire(void **buf); /** * @brief For write. Add one buffer * @param[in] buf upstream name * @param[in] buflen consistent-hash functional * @param[in] mode BUFFER_MODE_XXX * @return false if OOM * @note BUFFER_MODE_NOCOPY mean in deconstructor ignore this buffer * @note BUFFER_MODE_COPY mean memcpy this buffer right here at once * @note BUFFER_MODE_GIFT_NEW mean in deconstructor delete this buffer * @note BUFFER_MODE_GIFT_MALLOC mean in deconstructor free this buffer */ bool append(void *buf, size_t buflen, int mode); bool append(const void *buf, size_t buflen, int mode); /** * @brief For write. Backs up a number of bytes of last buffer * @param[in] count how many bytes back up * @return the actual bytes backup * @note It is affect the buffer with both acquire and append * @note count should be less than or equal to the size of the last buffer */ size_t backup(size_t count); /** * @brief For write. Add one buffer use memcpy * @param[in] buf buffer * @param[in] buflen buffer size * @return false if OOM */ bool write(const void *buf, size_t buflen); public: /** * @brief For workflow message encode. * @param[in] iov iov vector * @param[in] count iov vector count * @return use how many iov * @retval -1 when count <=0, and set errno EINVAL */ int encode(struct iovec *iov, int count); /** * @brief merge all buffer into one piece * @param[out] iov pointer and length of result * @return suceess or OOM * @retval 0 success * @retval -1 OOM */ int merge_all(struct iovec& iov); public: /** * @brief For read. Reset buffer position to head * @note NEVER fail */ void rewind(); /** * @brief For read. Get one buffer, NO move offset * @param[in,out] buf a pointer to a buffer * @return 0 if no more data to read, or the forward size available */ size_t peek(const void **buf); /** * @brief For read. Get one buffer by except size, move offset * @param[in,out] buf a pointer to a buffer * @param[in,out] size except buffer size, and return actual size * @return false if OOM, or your will get a buffer actual size <=expect-size */ bool fetch(const void **buf, size_t *size); /** * @brief For read. Get one buffer, move offset * @param[in,out] buf a pointer to a buffer * @return 0 if no more data to read, or the forward size available */ size_t fetch(const void **buf); /** * @brief For read. Fill one buffer with memcpy, move offset * @param[in] buf buffer wait to fill * @param[in] buflen except buffer size * @return true if fill buffer exactly bytes, false if no more data to read */ bool read(void *buf, size_t buflen); /** * @brief For read. move offset, positive mean skip, negative mean backward * @param[in] offset except move offset * @return actual move offset * @note If offset=0, do nothing at all */ long seek(long offset); public: void set_piece_min_size(size_t size) { piece_min_size_ = size; } void set_piece_max_size(size_t size) { piece_max_size_ = size; } RPCBuffer() = default; ~RPCBuffer(); RPCBuffer(const RPCBuffer&) = delete; RPCBuffer(RPCBuffer&&) = delete; RPCBuffer& operator=(const RPCBuffer&) = delete; RPCBuffer& operator=(RPCBuffer&&) = delete; private: struct buffer_t { void *buf; size_t buflen; bool is_nocopy; bool is_new; }; void clear_list_buffer(); size_t internal_fetch(const void **buf, bool move_or_stay); long read_skip(long offset); long read_back(long offset); std::list buffer_list_; std::pair::iterator, size_t> cur_; size_t size_ = 0; size_t piece_min_size_ = BUFFER_PIECE_MIN_SIZE; size_t piece_max_size_ = BUFFER_PIECE_MAX_SIZE; bool init_read_over_ = false; size_t last_piece_left_ = 0; }; } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_client.h000066400000000000000000000145531525217424400172400ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_CLIENT_H__ #define __RPC_CLIENT_H__ #include "rpc_types.h" #include "rpc_context.h" #include "rpc_options.h" #include "rpc_global.h" #include "rpc_trace_module.h" #include "rpc_metrics_module.h" namespace srpc { template class RPCClient { public: using TASK = RPCClientTask; protected: using COMPLEXTASK = WFComplexClientTask; public: RPCClient(const std::string& service_name); virtual ~RPCClient() { }; const RPCTaskParams *get_task_params() const; const std::string& get_service_name() const; void task_init(COMPLEXTASK *task) const; void set_keep_alive(int timeout); void set_watch_timeout(int timeout); void add_filter(RPCFilter *filter); protected: template TASK *create_rpc_client_task(const std::string& method_name, std::function&& done) { std::list module; for (int i = 0; i < SRPC_MODULE_MAX; i++) { if (this->modules[i]) module.push_back(this->modules[i]); } auto *task = new TASK(this->service_name, method_name, &this->params.task_params, std::move(module), [done](int status_code, RPCWorker& worker) -> int { return ClientRPCDoneImpl(status_code, worker, done); }); this->task_init(task); return task; } void init(const RPCClientParams *params); std::string service_name; private: void __task_init(COMPLEXTASK *task) const; protected: RPCClientParams params; ParsedURI uri; private: struct sockaddr_storage ss; socklen_t ss_len; bool has_addr_info; std::mutex mutex; RPCModule *modules[SRPC_MODULE_MAX] = { 0 }; }; //////// // inl template inline RPCClient::RPCClient(const std::string& service_name): params(RPC_CLIENT_PARAMS_DEFAULT), has_addr_info(false) { SRPCGlobal::get_instance(); this->service_name = service_name; } template inline const RPCTaskParams *RPCClient::get_task_params() const { return &this->params.task_params; } template inline const std::string& RPCClient::get_service_name() const { return this->service_name; } template inline void RPCClient::set_keep_alive(int timeout) { this->params.task_params.keep_alive_timeout = timeout; } template inline void RPCClient::set_watch_timeout(int timeout) { this->params.task_params.watch_timeout = timeout; } template void RPCClient::add_filter(RPCFilter *filter) { using CLIENT_TASK = RPCClientTask; using SERVER_TASK = RPCServerTask; int type = filter->get_module_type(); this->mutex.lock(); if (type < SRPC_MODULE_MAX && type >= 0) { RPCModule *module = this->modules[type]; if (!module) { switch (type) { case RPCModuleTypeTrace: module = new RPCTraceModule(); break; case RPCModuleTypeMetrics: module = new RPCMetricsModule(); break; case RPCModuleTypeCustom: module = new RPCCustomModule(); break; default: break; } this->modules[type] = module; } if (module) module->add_filter(filter); } this->mutex.unlock(); return; } template inline void RPCClient::init(const RPCClientParams *params) { this->params = *params; if (this->params.task_params.data_type == RPCDataUndefined) this->params.task_params.data_type = RPCTYPE::default_data_type; this->has_addr_info = SRPCGlobal::get_instance()->task_init(this->params, this->uri, &this->ss, &this->ss_len); if (this->params.is_ssl) { if (this->params.transport_type == TT_TCP) this->params.transport_type = TT_TCP_SSL; else if (this->params.transport_type == TT_SCTP) this->params.transport_type = TT_SCTP_SSL; } else if (this->params.transport_type == TT_TCP_SSL || this->params.transport_type == TT_SCTP_SSL) { this->params.is_ssl = true; } } template inline void RPCClient::__task_init(COMPLEXTASK *task) const { if (this->has_addr_info) { task->init(this->params.transport_type, (const struct sockaddr *)&this->ss, this->ss_len, ""); } else { task->init(this->uri); task->set_transport_type(this->params.transport_type); } } template inline void RPCClient::task_init(COMPLEXTASK *task) const { __task_init(task); } static inline void __set_host_by_uri(const ParsedURI *uri, bool is_ssl, std::string& header_host) { if (uri->host && uri->host[0]) header_host = uri->host; if (uri->port && uri->port[0]) { int port = atoi(uri->port); if (is_ssl) { if (port != 443) { header_host += ":"; header_host += uri->port; } } else { if (port != 80) { header_host += ":"; header_host += uri->port; } } } } template<> inline void RPCClient::task_init(COMPLEXTASK *task) const { __task_init(task); std::string header_host; if (this->has_addr_info) header_host += this->params.host + ":" + std::to_string(this->params.port); else __set_host_by_uri(task->get_current_uri(), this->params.is_ssl, header_host); task->get_req()->set_header_pair("Host", header_host.c_str()); } template<> inline void RPCClient::task_init(COMPLEXTASK *task) const { __task_init(task); std::string header_host; if (this->has_addr_info) header_host += this->params.host + ":" + std::to_string(this->params.port); else __set_host_by_uri(task->get_current_uri(), this->params.is_ssl, header_host); task->get_req()->set_header_pair("Host", header_host.c_str()); } } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_context.h000066400000000000000000000077141525217424400174470ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_CONTEXT_H__ #define __RPC_CONTEXT_H__ #include #include #include #include "rpc_basic.h" namespace srpc { struct RPCSyncContext { long long seqid; std::string errmsg; std::string remote_ip; int status_code; int error; bool success; int timeout_reason; }; class RPCContext { public: virtual long long get_seqid() const = 0; virtual std::string get_remote_ip() const = 0; virtual int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const = 0; virtual const std::string& get_service_name() const = 0; virtual const std::string& get_method_name() const = 0; virtual SeriesWork *get_series() const = 0; virtual bool get_http_header(const std::string& name, std::string& value) const = 0; public: // for client-done virtual bool success() const = 0; virtual int get_status_code() const = 0; virtual const char *get_errmsg() const = 0; virtual int get_error() const = 0; virtual void *get_user_data() const = 0; virtual int get_timeout_reason() const = 0; public: // for server-process virtual void set_data_type(RPCDataType type) = 0;//enum RPCDataType virtual void set_compress_type(RPCCompressType type) = 0;//enum RPCCompressType virtual void set_attachment_nocopy(const char *attachment, size_t len) = 0; virtual bool get_attachment(const char **attachment, size_t *len) const = 0; virtual void set_reply_callback(std::function cb) = 0; virtual void set_send_timeout(int timeout) = 0; virtual void set_keep_alive(int timeout) = 0; virtual bool set_http_code(int code) = 0; virtual bool set_http_header(const std::string& name, const std::string& value) = 0; virtual bool add_http_header(const std::string& name, const std::string& value) = 0; virtual bool log(const RPCLogVector& fields) = 0; // Refer to : https://opentelemetry.io/docs/reference/specification/baggage/api // corresponding to SetValue(), GetValue(), RemoveValue() virtual bool add_baggage(const std::string& key, const std::string& value) = 0; virtual bool get_baggage(const std::string& key, std::string& value) = 0; //virtual bool remove_baggage(const std::string& key) = 0; //virtual void noreply(); //virtual WFConnection *get_connection(); public: // for json format // Currently only support pb to json. Default : false // Whether to add spaces, line breaks and indentation to make the JSON // output easy to read. virtual void set_json_add_whitespace(bool on) = 0; // Whether to always print enums as ints. virtual void set_json_always_print_enums_as_ints(bool flag) = 0; // Whether to preserve proto field names. virtual void set_json_preserve_proto_field_names(bool flag) = 0; // Whether to always print primitive fields / with no presence. // By default proto3 primitive fields with default values will be omitted // in JSON output. For example, an int32 field set to 0 will be omitted. // Set this flag to true will override the default behavior and print // primitive fields regardless of their values. virtual void set_json_always_print_fields_with_no_presence(bool flag) = 0; // deprecated : Please use set_json_always_print_fields_with_no_presence() void set_json_always_print_primitive_fields(bool flag) { this->set_json_always_print_fields_with_no_presence(flag); } public: virtual ~RPCContext() { } }; } // namespace srpc //////// // inl #include "rpc_context.inl" #include "rpc_task.inl" #endif sogou-srpc-ac65014/src/rpc_context.inl000066400000000000000000000142331525217424400177740ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #include "rpc_message.h" #include "rpc_module.h" namespace srpc { template struct ThriftReceiver { std::mutex mutex; std::condition_variable cond; RPCSyncContext ctx; T output; bool is_done = false; }; template class RPCContextImpl : public RPCContext { public: long long get_seqid() const override { return task_->get_task_seq(); } std::string get_remote_ip() const override { char ip_str[INET6_ADDRSTRLEN + 1] = { 0 }; struct sockaddr_storage addr; socklen_t addrlen = sizeof (addr); if (this->get_peer_addr((struct sockaddr *)&addr, &addrlen) == 0) { if (addr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)(&addr); inet_ntop(AF_INET, &sin->sin_addr, ip_str, INET_ADDRSTRLEN); } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(&addr); inet_ntop(AF_INET6, &sin6->sin6_addr, ip_str, INET6_ADDRSTRLEN); } } return std::string(ip_str); } int get_peer_addr(struct sockaddr *addr, socklen_t *addrlen) const override { return task_->get_peer_addr(addr, addrlen); } const std::string& get_service_name() const override { return task_->get_req()->get_service_name(); } const std::string& get_method_name() const override { return task_->get_req()->get_method_name(); } void set_data_type(RPCDataType type) override { task_->get_resp()->set_data_type(type); } void set_compress_type(RPCCompressType type) override { task_->get_resp()->set_compress_type(type); } void set_send_timeout(int timeout) override { task_->set_send_timeout(timeout); } void set_keep_alive(int timeout) override { task_->set_keep_alive(timeout); } SeriesWork *get_series() const override { return series_of(task_); } void *get_user_data() const override { return task_->user_data; } bool get_attachment(const char **attachment, size_t *len) const override { if (this->is_server_task()) return task_->get_req()->get_attachment_nocopy(attachment, len); else return task_->get_resp()->get_attachment_nocopy(attachment, len); } bool get_http_header(const std::string& name, std::string& value) const override { if (this->is_server_task()) return task_->get_req()->get_http_header(name, value); else return task_->get_resp()->get_http_header(name, value); } public: // for client-done bool success() const override { return task_->get_resp()->get_status_code() == RPCStatusOK; } int get_status_code() const override { return task_->get_resp()->get_status_code(); } const char *get_errmsg() const override { return task_->get_resp()->get_errmsg(); } int get_error() const override { return task_->get_resp()->get_error(); } int get_timeout_reason() const override { return task_->get_timeout_reason(); } public: // for server-process void set_attachment_nocopy(const char *attachment, size_t len) override { task_->get_resp()->set_attachment_nocopy(attachment, len); } void set_reply_callback(std::function cb) override { if (this->is_server_task()) { if (cb) { task_->set_callback([this, cb](SubTask *task) { cb(this); }); } else task_->set_callback(nullptr); } } bool set_http_code(int code) override { if (this->is_server_task()) return task_->get_resp()->set_http_code(code); return false; } bool set_http_header(const std::string& name, const std::string& value) override { if (this->is_server_task()) return task_->get_resp()->set_http_header(name, value); return false; } bool add_http_header(const std::string& name, const std::string& value) override { if (this->is_server_task()) return task_->get_resp()->add_http_header(name, value); return false; } bool log(const RPCLogVector& fields) override { if (this->is_server_task() && module_data_) { std::string key; std::string value; RPCCommon::log_format(key, value, fields); module_data_->emplace(std::move(key), std::move(value)); return true; } return false; } bool add_baggage(const std::string& key, const std::string& value) override { if (this->is_server_task() && module_data_) { (*module_data_)[key] = value; return true; } return false; } bool get_baggage(const std::string& key, std::string& value) override { if (module_data_) { const auto it = module_data_->find(key); if (it != module_data_->cend()) { value = it->second; return true; } } return false; } void set_json_add_whitespace(bool on) override { if (this->is_server_task()) task_->get_resp()->set_json_add_whitespace(on); } void set_json_always_print_enums_as_ints(bool on) override { if (this->is_server_task()) task_->get_resp()->set_json_enums_as_ints(on); } void set_json_preserve_proto_field_names(bool on) override { if (this->is_server_task()) task_->get_resp()->set_json_preserve_names(on); } void set_json_always_print_fields_with_no_presence(bool on) override { if (this->is_server_task()) task_->get_resp()->set_json_fields_no_presence(on); } //void noreply() override; //WFConnection *get_connection() override; public: RPCContextImpl(WFNetworkTask *task, RPCModuleData *module_data) : task_(task), module_data_(module_data) { } protected: bool is_server_task() const { return task_->get_state() == WFT_STATE_TOREPLY || task_->get_state() == WFT_STATE_NOREPLY; } protected: WFNetworkTask *task_; private: RPCModuleData *module_data_; }; } // namespace srpc sogou-srpc-ac65014/src/rpc_define.h000066400000000000000000000035001525217424400172020ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_DEFINE_H__ #define __RPC_DEFINE_H__ #if __cplusplus < 201100 #error CPLUSPLUS VERSION required at least C++11. Please use "-std=c++11". #include #endif #include "rpc_server.h" #include "rpc_client.h" namespace srpc { using SRPCServer = RPCServer; using SRPCClient = RPCClient; using SRPCClientTask = SRPCClient::TASK; using SRPCHttpServer = RPCServer; using SRPCHttpClient = RPCClient; using SRPCHttpClientTask = SRPCHttpClient::TASK; using BRPCServer = RPCServer; using BRPCClient = RPCClient; using BRPCClientTask = BRPCClient::TASK; using ThriftServer = RPCServer; using ThriftClient = RPCClient; using ThriftClientTask = ThriftClient::TASK; using ThriftHttpServer = RPCServer; using ThriftHttpClient = RPCClient; using ThriftHttpClientTask = ThriftHttpClient::TASK; using TRPCServer = RPCServer; using TRPCClient = RPCClient; using TRPCClientTask = TRPCClient::TASK; using TRPCHttpServer = RPCServer; using TRPCHttpClient = RPCClient; using TRPCHttpClientTask = TRPCHttpClient::TASK; } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_global.cc000066400000000000000000000060641525217424400173560ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifdef _WIN32 #include #else #include #include #include #endif #include #include #include "rpc_basic.h" #include "rpc_global.h" namespace srpc { SRPCGlobal::SRPCGlobal() : gen(rd()) { WFGlobal::register_scheme_port(SRPC_SCHEME, SRPC_DEFAULT_PORT); WFGlobal::register_scheme_port(SRPC_SSL_SCHEME, SRPC_SSL_DEFAULT_PORT); this->group_id = this->gen() % (1 << SRPC_GROUP_BITS); this->machine_id = this->gen() % (1 << SRPC_MACHINE_BITS); } static int __get_addr_info(const std::string& host, unsigned short port, struct sockaddr_storage *ss, socklen_t *ss_len) { if (!host.empty()) { char front = host.front(); char back = host.back(); if (host.find(':') != std::string::npos) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ss; memset(sin6, 0, sizeof (struct sockaddr_in6)); if (inet_pton(AF_INET6, host.c_str(), &sin6->sin6_addr) == 1) { sin6->sin6_family = AF_INET6; sin6->sin6_port = htons(port); *ss_len = sizeof (struct sockaddr_in6); return 0; } } else if (isdigit(back) && isdigit(front)) { struct sockaddr_in *sin = (struct sockaddr_in *)ss; memset(sin, 0, sizeof (struct sockaddr_in)); if (inet_pton(AF_INET, host.c_str(), &sin->sin_addr) == 1) { sin->sin_family = AF_INET; sin->sin_port = htons(port); *ss_len = sizeof (struct sockaddr_in); return 0; } } } return -1; } bool SRPCGlobal::task_init(RPCClientParams& params, ParsedURI& uri, struct sockaddr_storage *ss, socklen_t *ss_len) const { if (!params.host.empty()) { if (__get_addr_info(params.host, params.port, ss, ss_len) == 0) return true; if (params.is_ssl) uri.scheme = strdup(SRPC_SSL_SCHEME); else uri.scheme = strdup(SRPC_SCHEME); uri.host = strdup(params.host.c_str()); uri.port = strdup(std::to_string(params.port).c_str()); if (uri.scheme && uri.host && uri.port) uri.state = URI_STATE_SUCCESS; else { uri.state = URI_STATE_ERROR; uri.error = errno; } } else { URIParser::parse(params.url, uri); params.is_ssl = (uri.scheme && (strcasecmp(uri.scheme, "https") == 0 || strcasecmp(uri.scheme, "srpcs") == 0)); } return false; } unsigned long long SRPCGlobal::get_random() { unsigned long long id = 0; this->snowflake.get_id(this->group_id, this->machine_id, &id); return id; } static const SRPCGlobal *srpc_global = SRPCGlobal::get_instance(); } // namespace srpc sogou-srpc-ac65014/src/rpc_global.h000066400000000000000000000025471525217424400172220ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_GLOBAL_H__ #define __RPC_GLOBAL_H__ #include #include #include "rpc_options.h" #include "rpc_module.h" namespace srpc { class SRPCGlobal { public: static SRPCGlobal *get_instance() { static SRPCGlobal kInstance; return &kInstance; } public: const char *get_srpc_version() const; bool task_init(RPCClientParams& params, ParsedURI& uri, struct sockaddr_storage *ss, socklen_t *ss_len) const; unsigned long long get_random(); void set_group_id(unsigned short id) { this->group_id = id; } void set_machine_id(unsigned short id) { this->machine_id = id; } private: SRPCGlobal(); SnowFlake snowflake; std::random_device rd; std::mt19937 gen; unsigned short group_id; unsigned short machine_id; }; } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_options.h000066400000000000000000000040211525217424400174420ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_OPTIONS_H__ #define __RPC_OPTIONS_H__ #include #include #include #include #include "rpc_basic.h" namespace srpc { struct RPCTaskParams { int send_timeout; int receive_timeout; int watch_timeout; int keep_alive_timeout; int retry_max; int compress_type; //RPCCompressType int data_type; //RPCDataType }; struct RPCClientParams { RPCTaskParams task_params; enum TransportType transport_type; //host + port + is_ssl std::string host; unsigned short port; bool is_ssl; //or URL std::string url; int callee_timeout; std::string caller; }; struct RPCServerParams : public WFServerParams { RPCServerParams() : WFServerParams(SERVER_PARAMS_DEFAULT) { this->request_size_limit = RPC_BODY_SIZE_LIMIT; } }; static constexpr struct RPCTaskParams RPC_TASK_PARAMS_DEFAULT = { /* .send_timeout = */ -1, /* .receive_timeout = */ -1, /* .watch_timeout = */ 0, /* .keep_alive_timeout = */ 30 * 1000, /* .retry_max = */ 0, /* .compress_type = */ RPCCompressNone, /* .data_type = */ RPCDataUndefined }; static const struct RPCClientParams RPC_CLIENT_PARAMS_DEFAULT = { /* .task_params = */ RPC_TASK_PARAMS_DEFAULT, /* .transport_type = */ TT_TCP, /* .host = */ "", /* .port = */ SRPC_DEFAULT_PORT, /* .is_ssl = */ false, /* .url = */ "", /* .callee_timeout = */ -1, /* .caller = */ "" }; static const RPCServerParams RPC_SERVER_PARAMS_DEFAULT; } // end namespace #endif sogou-srpc-ac65014/src/rpc_server.h000066400000000000000000000153451525217424400172700ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_SERVER_H__ #define __RPC_SERVER_H__ #include #include #include #include #include #include "rpc_types.h" #include "rpc_service.h" #include "rpc_options.h" #include "rpc_trace_module.h" #include "rpc_metrics_module.h" namespace srpc { template class RPCServer : public WFServer { public: using REQTYPE = typename RPCTYPE::REQ; using RESPTYPE = typename RPCTYPE::RESP; using TASK = RPCServerTask; using SERIES = typename TASK::RPCSeries; protected: using NETWORKTASK = WFNetworkTask; public: RPCServer(); RPCServer(const struct RPCServerParams *params); int add_service(RPCService *service); const RPCService* find_service(const std::string& name) const; void add_filter(RPCFilter *filter); protected: RPCServer(const struct RPCServerParams *params, std::function&& process); CommSession *new_session(long long seq, CommConnection *conn) override; void server_process(NETWORKTASK *task) const; private: std::mutex mutex; std::map service_map; RPCModule *modules[SRPC_MODULE_MAX] = { NULL }; }; //////// // inl template inline RPCServer::RPCServer(): WFServer(&RPC_SERVER_PARAMS_DEFAULT, std::bind(&RPCServer::server_process, this, std::placeholders::_1)) {} template inline RPCServer::RPCServer(const struct RPCServerParams *params): WFServer(params, std::bind(&RPCServer::server_process, this, std::placeholders::_1)) {} template inline RPCServer::RPCServer(const struct RPCServerParams *params, std::function&& process): WFServer(¶ms, std::move(process)) {} template inline int RPCServer::add_service(RPCService* service) { const auto it = this->service_map.emplace(service->get_name(), service); if (!it.second) { errno = EEXIST; return -1; } return 0; } template<> inline int RPCServer::add_service(RPCService* service) { const std::string &name = service->get_name(); const auto it = this->service_map.emplace(name, service); if (!it.second) { errno = EEXIST; return -1; } auto pos = name.find_last_of('.'); if (pos != std::string::npos) this->service_map.emplace(name.substr(pos + 1), service); return 0; } template<> inline int RPCServer::add_service(RPCService* service) { const std::string &name = service->get_name(); const auto it = this->service_map.emplace(name, service); if (!it.second) { errno = EEXIST; return -1; } auto pos = name.find_last_of('.'); if (pos != std::string::npos) this->service_map.emplace(name.substr(pos + 1), service); return 0; } template void RPCServer::add_filter(RPCFilter *filter) { using CLIENT_TASK = RPCClientTask; using SERVER_TASK = RPCServerTask; int type = filter->get_module_type(); this->mutex.lock(); if (type < SRPC_MODULE_MAX && type >= 0) { RPCModule *module = this->modules[type]; if (!module) { switch (type) { case RPCModuleTypeTrace: module = new RPCTraceModule(); break; case RPCModuleTypeMetrics: module = new RPCMetricsModule(); break; case RPCModuleTypeCustom: module = new RPCCustomModule(); break; default: break; } this->modules[type] = module; } if (module) module->add_filter(filter); } this->mutex.unlock(); return; } template inline const RPCService * RPCServer::find_service(const std::string& name) const { const auto it = this->service_map.find(name); if (it != this->service_map.cend()) return it->second; return NULL; } template inline CommSession *RPCServer::new_session(long long seq, CommConnection *conn) { /* TODO: Change to a factory function. */ std::list module; for (int i = 0; i < SRPC_MODULE_MAX; i++) { if (this->modules[i]) module.push_back(this->modules[i]); } auto *task = new TASK(this, this->process, std::move(module)); task->set_keep_alive(this->params.keep_alive_timeout); task->get_req()->set_size_limit(this->params.request_size_limit); return task; } template void RPCServer::server_process(NETWORKTASK *task) const { auto *req = task->get_req(); auto *resp = task->get_resp(); int status_code; do { if (!req->deserialize_meta()) { status_code = RPCStatusMetaError; break; } RPCTYPE::server_reply_init(req, resp); auto *service = this->find_service(req->get_service_name()); if (!service) { status_code = RPCStatusServiceNotFound; break; } auto *rpc = service->find_method(req->get_method_name()); if (!rpc) { status_code = RPCStatusMethodNotFound; break; } status_code = req->decompress(); if (status_code != RPCStatusOK) break; auto *server_task = static_cast(task); RPCModuleData *task_data = server_task->mutable_module_data(); req->get_meta_module_data(*task_data); for (auto *module : this->modules) { if (module && !module->server_task_begin(server_task, *task_data)) { status_code = RPCStatusModuleFilterFailed; break; } } if (status_code == RPCStatusOK) status_code = (*rpc)(server_task->worker); SERIES *series = static_cast(series_of(task)); series->set_module_data(task_data); } while (0); resp->set_status_code(status_code); } template<> inline const RPCService * RPCServer::find_service(const std::string& name) const { if (this->service_map.empty()) return NULL; return this->service_map.cbegin()->second; } template<> inline const RPCService * RPCServer::find_service(const std::string& name) const { if (this->service_map.empty()) return NULL; return this->service_map.cbegin()->second; } } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_service.h000066400000000000000000000044171525217424400174200ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_SERVICE_H__ #define __RPC_SERVICE_H__ #include #include #include #include "rpc_context.h" #include "rpc_options.h" namespace srpc { class RPCService { protected: using rpc_method_t = std::function; public: RPCService(const std::string& name) : name_(name) { } RPCService(RPCService&& move) = delete; RPCService& operator=(RPCService&& move) = delete; RPCService(const RPCService& copy) = delete; RPCService& operator=(const RPCService& copy) = delete; virtual ~RPCService() { }; const std::string& get_name() const { return name_; } const rpc_method_t *find_method(const std::string& method_name) const; protected: void add_method(const std::string& method_name, rpc_method_t&& method); private: std::unordered_map methods_; std::string name_; }; //////// // inl template static inline int ServiceRPCCallImpl(SERVICE *service, RPCWorker& worker, void (SERVICE::*rpc)(INPUT *, OUTPUT *, RPCContext *)) { auto *in = new INPUT; worker.set_server_input(in); int status_code = worker.req->deserialize(in); if (status_code == RPCStatusOK) { auto *out = new OUTPUT; worker.set_server_output(out); (service->*rpc)(in, out, worker.ctx); } return status_code; } inline void RPCService::add_method(const std::string& method_name, rpc_method_t&& method) { methods_.emplace(method_name, std::move(method)); } inline const RPCService::rpc_method_t *RPCService::find_method(const std::string& method_name) const { const auto it = methods_.find(method_name); if (it != methods_.cend()) return &it->second; return NULL; } } // namespace srpc #endif sogou-srpc-ac65014/src/rpc_task.inl000066400000000000000000000441441525217424400172560ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include "rpc_basic.h" #include "rpc_message.h" #include "rpc_options.h" #include "rpc_global.h" namespace srpc { class RPCWorker { public: RPCWorker(RPCContext *ctx, RPCMessage *req, RPCMessage *resp) { this->ctx = ctx; this->req = req; this->resp = resp; this->__server_serialize = NULL; } ~RPCWorker() { delete this->ctx; delete this->pb_input; delete this->pb_output; delete this->thrift_intput; delete this->thrift_output; } void set_server_input(ProtobufIDLMessage *input) { this->pb_input = input; } void set_server_input(ThriftIDLMessage *input) { this->thrift_intput = input; } void set_server_output(ProtobufIDLMessage *output) { this->pb_output = output; this->__server_serialize = &RPCWorker::resp_serialize_pb; } void set_server_output(ThriftIDLMessage *output) { this->thrift_output = output; this->__server_serialize = &RPCWorker::resp_serialize_thrift; } int server_serialize() { if (!this->__server_serialize) return RPCStatusOK; return (this->*__server_serialize)(); } public: RPCContext *ctx; RPCMessage *req; RPCMessage *resp; private: int resp_serialize_pb() { return this->resp->serialize(this->pb_output); } int resp_serialize_thrift() { return this->resp->serialize(this->thrift_output); } int (RPCWorker::*__server_serialize)(); ProtobufIDLMessage *pb_input = NULL; ProtobufIDLMessage *pb_output = NULL; ThriftIDLMessage *thrift_intput = NULL; ThriftIDLMessage *thrift_output = NULL; }; template class RPCClientTask : public WFComplexClientTask { public: // before rpc call void set_data_type(RPCDataType type); void set_compress_type(RPCCompressType type); void set_retry_max(int retry_max); void set_attachment_nocopy(const char *attachment, size_t len); int set_uri_fragment(const std::string& fragment); int serialize_input(const ProtobufIDLMessage *in); int serialize_input(const ThriftIDLMessage *in); // similar to opentracing: log({{"event", "error"}, {"message", "application log"}}); void log(const RPCLogVector& fields); // Baggage Items, which are just key:value pairs that cross process boundaries void add_baggage(const std::string& key, const std::string& value); bool get_baggage(const std::string& key, std::string& value); bool set_http_header(const std::string& name, const std::string& value); bool add_http_header(const std::string& name, const std::string& value); // JsonPrintOptions void set_json_add_whitespace(bool on); void set_json_always_print_enums_as_ints(bool on); void set_json_preserve_proto_field_names(bool on); // deprecated : Please use set_json_always_print_fields_with_no_presence() void set_json_always_print_primitive_fields(bool on); void set_json_always_print_fields_with_no_presence(bool on); protected: using user_done_t = std::function; using WFComplexClientTask::set_callback; void init_failed() override; bool check_request() override; CommMessageOut *message_out() override; bool finish_once() override; void rpc_callback(WFNetworkTask *task); int first_timeout() override { return watch_timeout_; } public: RPCClientTask(const std::string& service_name, const std::string& method_name, const RPCTaskParams *params, std::list&& modules, user_done_t&& user_done); bool get_remote(std::string& ip, unsigned short *port) const; RPCModuleData *mutable_module_data() { return &module_data_; } void set_module_data(RPCModuleData data) { module_data_ = std::move(data); } private: template int __serialize_input(const IDL *in); user_done_t user_done_; bool init_failed_; int watch_timeout_; RPCModuleData module_data_; std::list modules_; }; template class RPCServerTask : public WFServerTask { public: RPCServerTask(CommService *service, std::function *)>& process, std::list&& modules) : WFServerTask(service, WFGlobal::get_scheduler(), process), worker(new RPCContextImpl(this, &module_data_), &this->req, &this->resp), modules_(std::move(modules)) { } public: class RPCSeries : public WFServerTask::Series { public: RPCSeries(WFServerTask *task) : WFServerTask::Series(task), module_data(NULL) {} RPCModuleData *get_module_data() { return this->module_data; } void set_module_data(RPCModuleData *data) { this->module_data = data; } virtual void *get_specific(const char *key) { if (strcmp(key, SRPC_MODULE_DATA) == 0) return this->module_data; else return NULL; } private: RPCModuleData *module_data; }; protected: CommMessageOut *message_out() override; void handle(int state, int error) override; public: bool get_remote(std::string& ip, unsigned short *port) const; RPCModuleData *mutable_module_data() { return &module_data_; } void set_module_data(RPCModuleData data) { module_data_ = std::move(data); } public: RPCWorker worker; private: RPCModuleData module_data_; std::list modules_; }; template static void RPCAsyncFutureCallback(OUTPUT *output, srpc::RPCContext *ctx) { using RESULT = std::pair; auto *pr = static_cast *>(ctx->get_user_data()); RESULT res; res.second.seqid = ctx->get_seqid(); res.second.errmsg = ctx->get_errmsg(); res.second.remote_ip = ctx->get_remote_ip(); res.second.status_code = ctx->get_status_code(); res.second.error = ctx->get_error(); res.second.success = ctx->success(); res.second.timeout_reason = ctx->get_timeout_reason(); if (res.second.success) res.first = std::move(*output); pr->set_value(std::move(res)); delete pr; } template static void ThriftSendCallback(OUTPUT *output, srpc::RPCContext *ctx) { auto *receiver = static_cast *>(ctx->get_user_data()); receiver->mutex.lock(); receiver->ctx.seqid = ctx->get_seqid(); receiver->ctx.errmsg = ctx->get_errmsg(); receiver->ctx.remote_ip = ctx->get_remote_ip(); receiver->ctx.status_code = ctx->get_status_code(); receiver->ctx.error = ctx->get_error(); receiver->ctx.success = ctx->success(); if (receiver->ctx.success) receiver->output = std::move(*output); receiver->is_done = true; receiver->cond.notify_one(); receiver->mutex.unlock(); } template static inline int ClientRPCDoneImpl(int status_code, RPCWorker& worker, const std::function& rpc_done) { if (status_code == RPCStatusOK) { OUTPUT out; status_code = worker.resp->deserialize(&out); if (status_code == RPCStatusOK) rpc_done(&out, worker.ctx); return status_code; } rpc_done(NULL, worker.ctx); return status_code; } template CommMessageOut *RPCServerTask::message_out() { int status_code = this->worker.server_serialize(); if (status_code == RPCStatusOK) status_code = this->resp.compress(); if (status_code == RPCStatusOK) { if (!this->resp.serialize_meta()) status_code = RPCStatusMetaError; } if (this->resp.get_status_code() == RPCStatusOK) this->resp.set_status_code(status_code); // for server, this is the where series->module_data stored RPCModuleData *data = this->mutable_module_data(); for (auto *module : modules_) { if (!module->server_task_end(this, *data)) { status_code = RPCStatusModuleFilterFailed; break; } } if (status_code == RPCStatusOK) { this->resp.set_meta_module_data(*data); return this->WFServerTask::message_out(); } errno = EBADMSG; return NULL; } template void RPCServerTask::handle(int state, int error) { if (state != WFT_STATE_TOREPLY) return WFServerTask::handle(state, error); this->state = WFT_STATE_TOREPLY; this->target = this->get_target(); RPCSeries *series = new RPCSeries(this); series->start(); } template inline void RPCClientTask::set_data_type(RPCDataType type) { this->req.set_data_type(type); } template inline void RPCClientTask::set_compress_type(RPCCompressType type) { this->req.set_compress_type(type); } template inline void RPCClientTask::set_attachment_nocopy(const char *attachment, size_t len) { this->req.set_attachment_nocopy(attachment, len); } template int RPCClientTask::set_uri_fragment(const std::string& fragment) { char *str = strdup(fragment.c_str()); if (str) { free(this->uri_.fragment); this->uri_.fragment = str; return 0; } return -1; } template inline void RPCClientTask::set_retry_max(int retry_max) { this->retry_max_ = retry_max; } template inline int RPCClientTask::serialize_input(const ProtobufIDLMessage *in) { return __serialize_input(in); } template inline int RPCClientTask::serialize_input(const ThriftIDLMessage *in) { return __serialize_input(in); } template template inline int RPCClientTask::__serialize_input(const IDL *in) { if (init_failed_ == false) { int status_code = this->req.serialize(in); this->resp.set_status_code(status_code); if (status_code == RPCStatusOK) return 0; } return -1; } template inline RPCClientTask::RPCClientTask( const std::string& service_name, const std::string& method_name, const RPCTaskParams *params, std::list&& modules, user_done_t&& user_done): WFComplexClientTask(0, nullptr), user_done_(std::move(user_done)), init_failed_(false), modules_(std::move(modules)) { if (user_done_) this->set_callback(std::bind(&RPCClientTask::rpc_callback, this, std::placeholders::_1)); this->set_send_timeout(params->send_timeout); this->set_receive_timeout(params->receive_timeout); watch_timeout_ = params->watch_timeout; this->set_keep_alive(params->keep_alive_timeout); this->set_retry_max(params->retry_max); if (params->compress_type != RPCCompressNone) this->req.set_compress_type(params->compress_type); if (params->data_type != RPCDataUndefined) this->req.set_data_type(params->data_type); this->req.set_service_name(service_name); this->req.set_method_name(method_name); } template void RPCClientTask::init_failed() { init_failed_ = true; } template bool RPCClientTask::check_request() { int status_code = this->resp.get_status_code(); if (status_code != RPCStatusOK && status_code != RPCStatusUndefined) return false; void *series_data = series_of(this)->get_specific(SRPC_MODULE_DATA); RPCModuleData *data = (RPCModuleData *)series_data; if (data) this->set_module_data(*data); data = this->mutable_module_data(); for (auto *module : modules_) { if (!module->client_task_begin(this, *data)) { this->resp.set_status_code(RPCStatusModuleFilterFailed); return false; } } this->req.set_meta_module_data(*data); return true; } template CommMessageOut *RPCClientTask::message_out() { this->req.set_seqid(this->get_task_seq()); int status_code = this->req.compress(); if (status_code == RPCStatusOK) { if (!this->req.serialize_meta()) status_code = RPCStatusMetaError; } if (status_code == RPCStatusOK) return this->WFClientTask::message_out(); this->disable_retry(); this->resp.set_status_code(status_code); errno = EBADMSG; return NULL; } template bool RPCClientTask::finish_once() { int status_code = this->resp.get_status_code(); if (this->state == WFT_STATE_SUCCESS && (status_code == RPCStatusOK || status_code == RPCStatusUndefined)) { if (this->resp.deserialize_meta() == false) this->resp.set_status_code(RPCStatusMetaError); } return true; } template void RPCClientTask::rpc_callback(WFNetworkTask *task) { RPCWorker worker(new RPCContextImpl(this, &module_data_), &this->req, &this->resp); int status_code = this->resp.get_status_code(); if (status_code != RPCStatusOK && status_code != RPCStatusUndefined) { this->state = WFT_STATE_TASK_ERROR; this->error = status_code; } else if (this->state == WFT_STATE_SUCCESS) { status_code = this->resp.decompress(); if (status_code == RPCStatusOK) { this->resp.set_status_code(RPCStatusOK); status_code = user_done_(status_code, worker); } if (status_code != RPCStatusOK) { this->state = WFT_STATE_TASK_ERROR; this->error = status_code; } } if (this->state == WFT_STATE_TASK_ERROR) { switch (this->error) { case WFT_ERR_URI_PARSE_FAILED: case WFT_ERR_URI_SCHEME_INVALID: case WFT_ERR_URI_PORT_INVALID: status_code = RPCStatusURIInvalid; break; case WFT_ERR_UPSTREAM_UNAVAILABLE: status_code = RPCStatusUpstreamFailed; break; default: break; } } else if (this->state != WFT_STATE_SUCCESS) { switch (this->state) { case WFT_STATE_SYS_ERROR: status_code = RPCStatusSystemError; break; case WFT_STATE_SSL_ERROR: status_code = RPCStatusSSLError; break; case WFT_STATE_DNS_ERROR: status_code = RPCStatusDNSError; break; case WFT_STATE_ABORTED: status_code = RPCStatusProcessTerminated; break; default: status_code = RPCStatusUndefined; break; } } this->resp.set_status_code(status_code); this->resp.set_error(this->error); if (!modules_.empty()) { void *series_data; RPCModuleData *resp_data = this->mutable_module_data(); if (resp_data->empty()) // get series module data failed previously { series_data = series_of(this)->get_specific(SRPC_MODULE_DATA); if (series_data) resp_data = (RPCModuleData *)series_data; } // else // this->resp.get_meta_module_data(resp_data); for (auto *module : modules_) { // do not affect status_code, which is important for user_done_ if (!module->client_task_end(this, *resp_data)) break; } } if (status_code != RPCStatusOK) user_done_(status_code, worker); } template bool RPCClientTask::get_remote(std::string& ip, unsigned short *port) const { struct sockaddr_storage addr; socklen_t addrlen = sizeof (addr); char buf[INET6_ADDRSTRLEN + 1] = { 0 }; if (this->get_peer_addr((struct sockaddr *)&addr, &addrlen) == 0 && RPCCommon::addr_to_string((struct sockaddr *)&addr, buf, INET6_ADDRSTRLEN + 1, port) == true) { ip = buf; return true; } return false; } template bool RPCServerTask::get_remote(std::string& ip, unsigned short *port) const { struct sockaddr_storage addr; socklen_t addrlen = sizeof (addr); char buf[INET6_ADDRSTRLEN + 1] = { 0 }; if (this->get_peer_addr((struct sockaddr *)&addr, &addrlen) == 0 && RPCCommon::addr_to_string((struct sockaddr *)&addr, buf, INET6_ADDRSTRLEN + 1, port) == true) { ip = buf; return true; } return false; } template void RPCClientTask::log(const RPCLogVector& fields) { std::string key; std::string value; RPCCommon::log_format(key, value, fields); module_data_.insert(std::make_pair(std::move(key), std::move(value))); } template void RPCClientTask::add_baggage(const std::string& key, const std::string& value) { module_data_[key] = value; } template bool RPCClientTask::get_baggage(const std::string& key, std::string& value) { const auto it = module_data_.find(key); if (it != module_data_.cend()) { value = it->second; return true; } return false; } template inline void RPCClientTask::set_json_add_whitespace(bool on) { this->req.set_json_add_whitespace(on); } template inline void RPCClientTask::set_json_always_print_enums_as_ints(bool on) { this->req.set_json_enums_as_ints(on); } template inline void RPCClientTask::set_json_preserve_proto_field_names(bool on) { this->req.set_json_preserve_names(on); } template inline void RPCClientTask::set_json_always_print_primitive_fields(bool on) { this->req.set_json_fields_no_presence(on); } template inline void RPCClientTask::set_json_always_print_fields_with_no_presence(bool on) { this->req.set_json_fields_no_presence(on); } template inline bool RPCClientTask::set_http_header(const std::string& name, const std::string& value) { return this->req.set_http_header(name, value); } template inline bool RPCClientTask::add_http_header(const std::string& name, const std::string& value) { return this->req.add_http_header(name, value); } } // namespace srpc sogou-srpc-ac65014/src/rpc_types.h000066400000000000000000000066451525217424400171310ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_TYPE_H__ #define __RPC_TYPE_H__ #include "rpc_basic.h" #include "rpc_message.h" #include "rpc_message_srpc.h" #include "rpc_message_thrift.h" #include "rpc_message_brpc.h" #include "rpc_message_trpc.h" namespace srpc { struct RPCTYPESRPC { using REQ = SRPCStdRequest; using RESP = SRPCStdResponse; static constexpr RPCDataType default_data_type = RPCDataProtobuf; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_data_type(req->get_data_type()); } }; struct RPCTYPESRPCHttp { using REQ = SRPCHttpRequest; using RESP = SRPCHttpResponse; static constexpr RPCDataType default_data_type = RPCDataJson; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_data_type(req->get_data_type()); } }; struct RPCTYPEGRPC { //using REQ = GRPCHttp2Request; //using RESP = GRPCHttp2Response; static constexpr RPCDataType default_data_type = RPCDataProtobuf; }; struct RPCTYPEBRPC { using REQ = BRPCStdRequest; using RESP = BRPCStdResponse; static constexpr RPCDataType default_data_type = RPCDataProtobuf; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_correlation_id(req->get_correlation_id()); } }; struct RPCTYPEThrift { using REQ = ThriftStdRequest; using RESP = ThriftStdResponse; static constexpr RPCDataType default_data_type = RPCDataThrift; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_data_type(req->get_data_type()); auto *req_meta = req->get_meta(); auto *resp_meta = resp->get_meta(); resp_meta->is_strict = req_meta->is_strict; resp_meta->seqid = req_meta->seqid; resp_meta->method_name = req_meta->method_name; } }; struct RPCTYPEThriftHttp { using REQ = ThriftHttpRequest; using RESP = ThriftHttpResponse; static constexpr RPCDataType default_data_type = RPCDataThrift; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_data_type(req->get_data_type()); auto *req_meta = req->get_meta(); auto *resp_meta = resp->get_meta(); resp_meta->is_strict = req_meta->is_strict; resp_meta->seqid = req_meta->seqid; resp_meta->method_name = req_meta->method_name; } }; struct RPCTYPETRPC { using REQ = TRPCStdRequest; using RESP = TRPCStdResponse; static constexpr RPCDataType default_data_type = RPCDataProtobuf; static inline void server_reply_init(const REQ *req, RESP *resp) { const_cast(req)->trim_method_prefix(); resp->set_request_id(req->get_request_id()); } }; struct RPCTYPETRPCHttp { using REQ = TRPCHttpRequest; using RESP = TRPCHttpResponse; static constexpr RPCDataType default_data_type = RPCDataJson; static inline void server_reply_init(const REQ *req, RESP *resp) { resp->set_data_type(req->get_data_type()); const_cast(req)->trim_method_prefix(); resp->set_request_id(req->get_request_id()); } }; } // end namespace srpc #endif sogou-srpc-ac65014/src/rpc_zero_copy_stream.h000066400000000000000000000046731525217424400213500ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #ifndef __RPC_ZERO_COPY_STREAM_H__ #define __RPC_ZERO_COPY_STREAM_H__ #include namespace srpc { class RPCOutputStream : public google::protobuf::io::ZeroCopyOutputStream { public: RPCOutputStream(RPCBuffer *buf, size_t size); bool Next(void **data, int *size) override; void BackUp(int count) override; int64_t ByteCount() const override; private: RPCBuffer *buf; size_t size; }; class RPCInputStream : public google::protobuf::io::ZeroCopyInputStream { public: RPCInputStream(RPCBuffer *buf); bool Next(const void **data, int *size) override; void BackUp(int count) override; bool Skip(int count) override; int64_t ByteCount() const override; private: RPCBuffer *buf; }; inline RPCOutputStream::RPCOutputStream(RPCBuffer *buf, size_t size) { this->buf = buf; this->size = size; } inline bool RPCOutputStream::Next(void **data, int *size) { size_t tmp; if (this->size > 0) { tmp = this->size; if (this->buf->acquire(data, &tmp)) { this->size -= tmp; *size = (int)tmp; return true; } } else { tmp = this->buf->acquire(data); if (tmp > 0) { *size = (int)tmp; return true; } } return false; } inline void RPCOutputStream::BackUp(int count) { this->buf->backup(count); } inline int64_t RPCOutputStream::ByteCount() const { return this->buf->size(); } inline RPCInputStream::RPCInputStream(RPCBuffer *buf) { this->buf = buf; } inline bool RPCInputStream::Next(const void **data, int *size) { size_t len = this->buf->fetch(data); if (len == 0) return false; *size = (int)len; return true; } inline bool RPCInputStream::Skip(int count) { return this->buf->seek(count) == count ? true : false; } inline void RPCInputStream::BackUp(int count) { this->buf->seek(0 - count); } inline int64_t RPCInputStream::ByteCount() const { return (int64_t)this->buf->size(); } } // namespace srpc #endif sogou-srpc-ac65014/src/thrift/000077500000000000000000000000001525217424400162355ustar00rootroot00000000000000sogou-srpc-ac65014/src/thrift/CMakeLists.txt000066400000000000000000000002251525217424400207740ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(thrift) set(SRC rpc_thrift_buffer.cc rpc_thrift_idl.cc ) add_library(${PROJECT_NAME} OBJECT ${SRC}) sogou-srpc-ac65014/src/thrift/rpc_thrift_buffer.cc000066400000000000000000000134531525217424400222470ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include "rpc_thrift_buffer.h" #include "rpc_basic.h" namespace srpc { bool ThriftBuffer::readI08(int8_t& val) { return this->buffer->read((char *)&val, 1); } bool ThriftBuffer::readI16(int16_t& val) { if (!this->buffer->read((char *)&val, 2)) return false; val = ntohs(val); return true; } bool ThriftBuffer::readI32(int32_t& val) { if (!this->buffer->read((char *)&val, 4)) return false; val = ntohl(val); return true; } bool ThriftBuffer::readI64(int64_t& val) { if (!this->buffer->read((char *)&val, 8)) return false; val = ntohll(val); return true; } bool ThriftBuffer::readU64(uint64_t& val) { if (!this->buffer->read((char *)&val, 8)) return false; val = ntohll(val); return true; } bool ThriftBuffer::readFieldBegin(int8_t& field_type, int16_t& field_id) { if (!readI08(field_type)) return false; if (field_type == TDT_STOP) field_id = 0; else if (!readI16(field_id)) return false; return true; } bool ThriftBuffer::readString(std::string& str) { int32_t slen; if (!readI32(slen) || slen < 0) return false; if (!readStringBody(str, slen)) return false; return true; } bool ThriftBuffer::readStringBody(std::string& str, int32_t slen) { if (slen < 0) return false; str.resize(slen); return this->buffer->read(const_cast(str.c_str()), slen); } bool ThriftBuffer::writeFieldStop() { return writeI08((int8_t)TDT_STOP); } bool ThriftBuffer::writeI08(int8_t val) { return this->buffer->write((char *)&val, 1); } bool ThriftBuffer::writeI16(int16_t val) { int16_t x = htons(val); return this->buffer->write((char *)&x, 2); } bool ThriftBuffer::writeI32(int32_t val) { int32_t x = htonl(val); return this->buffer->write((char *)&x, 4); } bool ThriftBuffer::writeI64(int64_t val) { int64_t x = htonll(val); return this->buffer->write((char *)&x, 8); } bool ThriftBuffer::writeU64(uint64_t val) { uint64_t x = htonll(val); return this->buffer->write((char *)&x, 8); } bool ThriftBuffer::writeFieldBegin(int8_t field_type, int16_t field_id) { if (!writeI08(field_type)) return false; return writeI16(field_id); } bool ThriftBuffer::writeString(const std::string& str) { int32_t slen = (int32_t)str.size(); if (!writeI32(slen)) return false; return writeStringBody(str); } bool ThriftBuffer::writeStringBody(const std::string& str) { return this->buffer->write(str.c_str(), str.size()); } bool ThriftMeta::writeI08(int8_t val) { this->writebuf.append(1, (char)val); return true; } bool ThriftMeta::writeI32(int32_t val) { int32_t x = htonl(val); this->writebuf.append((const char *)&x, 4); return true; } bool ThriftMeta::writeString(const std::string& str) { int32_t slen = (int32_t)str.size(); writeI32(slen); if (slen > 0) this->writebuf.append(str); return true; } bool ThriftBuffer::readMessageBegin() { int32_t header; if (!readI32(header)) return false; if (header < 0) { int32_t version = header & THRIFT_VERSION_MASK; if (version != THRIFT_VERSION_1) return false;//bad version if (!readString(meta.method_name)) return false; if (!readI32(meta.seqid)) return false; meta.message_type = header & 0xFF; meta.is_strict = true; } else { if (!readStringBody(meta.method_name, header)) return false; if (!readI08(meta.message_type)) return false; if (!readI32(meta.seqid)) return false; meta.is_strict = false; } return true; } bool ThriftBuffer::writeMessageBegin() { if (meta.is_strict) { int32_t version = (THRIFT_VERSION_1) | ((int32_t)meta.message_type); if (!meta.writeI32(version)) return false; if (!meta.writeString(meta.method_name)) return false; if (!meta.writeI32(meta.seqid)) return false; } else { if (!meta.writeString(meta.method_name)) return false; if (!meta.writeI08(meta.message_type)) return false; if (!meta.writeI32(meta.seqid)) return false; } return true; } bool ThriftBuffer::skip(int8_t field_type) { switch (field_type) { case TDT_I08: case TDT_BOOL: return this->buffer->seek(1) == 1; case TDT_I16: return this->buffer->seek(2) == 2; case TDT_I32: return this->buffer->seek(4) == 4; case TDT_I64: case TDT_U64: case TDT_DOUBLE: return this->buffer->seek(8) == 8; case TDT_STRING: case TDT_UTF8: case TDT_UTF16: { int32_t slen; if (!readI32(slen) || slen < 0) return false; return this->buffer->seek(slen) == slen; } case TDT_STRUCT: { int8_t field_type; int16_t field_id; while (true) { if (!readFieldBegin(field_type, field_id)) return false; if (field_type == TDT_STOP) break; if (!skip(field_type)) return false; } break; } case TDT_MAP: { int8_t key_type; int8_t val_type; int32_t count; if (!readI08(key_type)) return false; if (!readI08(val_type)) return false; if (!readI32(count)) return false; for (int32_t i = 0; i < count; i++) { if (!skip(key_type)) return false; if (!skip(val_type)) return false; } break; } case TDT_LIST: case TDT_SET: { int8_t val_type; int32_t count; if (!readI08(val_type)) return false; if (!readI32(count)) return false; for (int32_t i = 0; i < count; i++) { if (!skip(val_type)) return false; } break; } default://return false?? break; } return true; } } // end namespace srpc sogou-srpc-ac65014/src/thrift/rpc_thrift_buffer.h000066400000000000000000000051771525217424400221150ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_THRIFT_BUFFER_H__ #define __RPC_THRIFT_BUFFER_H__ #include #include #include #include "rpc_thrift_enum.h" #include "rpc_buffer.h" namespace srpc { static constexpr int32_t THRIFT_VERSION_MASK = ((int32_t)0xffff0000); static constexpr int32_t THRIFT_VERSION_1 = ((int32_t)0x80010000); enum { //THRIFT_PARSE_INIT = 0, THRIFT_GET_FRAME_SIZE = 1, THRIFT_GET_DATA, THRIFT_PARSE_END }; class ThriftMeta { public: std::string writebuf; std::string method_name; int seqid = 0; int8_t message_type = TMT_CALL; bool is_strict = true; public: ThriftMeta() = default; ThriftMeta(const ThriftMeta&) = delete; ThriftMeta& operator= (const ThriftMeta&) = delete; ThriftMeta(ThriftMeta&& move) = delete; ThriftMeta& operator= (ThriftMeta &&move) = delete; bool writeI08(int8_t val); bool writeI32(int32_t val); bool writeString(const std::string& str); }; class ThriftBuffer { public: ThriftMeta meta; RPCBuffer *buffer; size_t readbuf_size = 0; size_t framesize_read_byte = 0; int32_t framesize = 0; int status = THRIFT_GET_FRAME_SIZE; public: ThriftBuffer(RPCBuffer *buf): buffer(buf) { } ThriftBuffer(const ThriftBuffer&) = delete; ThriftBuffer& operator= (const ThriftBuffer&) = delete; ThriftBuffer(ThriftBuffer&& move) = delete; ThriftBuffer& operator= (ThriftBuffer &&move) = delete; public: bool readMessageBegin(); bool readFieldBegin(int8_t& field_type, int16_t& field_id); bool readI08(int8_t& val); bool readI16(int16_t& val); bool readI32(int32_t& val); bool readI64(int64_t& val); bool readU64(uint64_t& val); bool readString(std::string& str); bool readStringBody(std::string& str, int32_t slen); bool skip(int8_t field_type); bool writeMessageBegin(); bool writeFieldBegin(int8_t field_type, int16_t field_id); bool writeFieldStop(); bool writeI08(int8_t val); bool writeI16(int16_t val); bool writeI32(int32_t val); bool writeI64(int64_t val); bool writeU64(uint64_t val); bool writeString(const std::string& str); bool writeStringBody(const std::string& str); }; } // end namespace srpc #endif sogou-srpc-ac65014/src/thrift/rpc_thrift_enum.h000066400000000000000000000031341525217424400215770ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_THRIFT_ENUM_H__ #define __RPC_THRIFT_ENUM_H__ namespace srpc { static constexpr int THRIFT_STRUCT_FIELD_REQUIRED = 0; static constexpr int THRIFT_STRUCT_FIELD_OPTIONAL = 1; static constexpr int THRIFT_STRUCT_FIELD_DEFAULT = 2; enum ThriftMessageType { TMT_CALL = 1, TMT_REPLY = 2, TMT_EXCEPTION = 3, TMT_ONEWAY = 4 }; enum ThriftDataType { TDT_STOP = 0, TDT_VOID = 1, TDT_BOOL = 2, TDT_BYTE = 3, TDT_I08 = 3, TDT_I16 = 6, TDT_I32 = 8, TDT_U64 = 9, TDT_I64 = 10, TDT_DOUBLE = 4, TDT_STRING = 11, TDT_UTF7 = 11, TDT_STRUCT = 12, TDT_MAP = 13, TDT_SET = 14, TDT_LIST = 15, TDT_UTF8 = 16, TDT_UTF16 = 17 }; enum ThriftExceptionType { TET_UNKNOWN = 0, TET_UNKNOWN_METHOD = 1, TET_INVALID_MESSAGE_TYPE = 2, TET_WRONG_METHOD_NAME = 3, TET_BAD_SEQUENCE_ID = 4, TET_MISSING_RESULT = 5, TET_INTERNAL_ERROR = 6, TET_PROTOCOL_ERROR = 7, TET_INVALID_TRANSFORM = 8, TET_INVALID_PROTOCOL = 9, TET_UNSUPPORTED_CLIENT_TYPE = 10 }; } // end namespace srpc #endif sogou-srpc-ac65014/src/thrift/rpc_thrift_idl.cc000066400000000000000000000253341525217424400215470ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include "rpc_thrift_idl.h" namespace srpc { static inline char __hex_ch(int x) { if (x >= 0 && x < 10) return x + '0'; else if (x >= 10 && x < 16) return x - 10 + 'A'; return '0'; } static inline int __hex_int(unsigned char ch) { if (ch >= '0' && ch <= '9') return ch - '0'; else if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; return -1; } bool ThriftJsonUtil::skip_whitespace(ThriftBuffer *buffer) { const void *buf; size_t buflen; while (buflen = buffer->buffer->peek(&buf), buf && buflen > 0) { char *base = (char *)buf; for (size_t i = 0; i < buflen; i++) { if (!isspace(base[i])) { buffer->buffer->seek(i); return true; } } buffer->buffer->seek(buflen); } return false; } bool ThriftJsonUtil::peek_first_meaningful_char(ThriftBuffer *buffer, char& ch) { if (!skip_whitespace(buffer)) return false; const void *buf; size_t buflen = buffer->buffer->peek(&buf); if (buf && buflen > 0) { ch = *((char *)buf); return true; } return false; } bool ThriftJsonUtil::skip_character(ThriftBuffer *buffer, char ch) { char mchar; if (!peek_first_meaningful_char(buffer, mchar)) return false; buffer->buffer->seek(1); return mchar == ch; } bool ThriftJsonUtil::skip_simple_string(ThriftBuffer *buffer, const std::string& str) { if (!skip_whitespace(buffer)) return false; size_t cur = 0; const void *buf; size_t buflen; while (cur < str.size() && (buflen = buffer->buffer->peek(&buf), buf && buflen > 0)) { size_t i = 0; char *base = (char *)buf; while (cur < str.size() && i < buflen) { if (base[i++] != str[cur++]) return false; } buffer->buffer->seek(i); } return cur == str.size(); } bool ThriftJsonUtil::read_int64(ThriftBuffer *buffer, int64_t& intv) { const void *buf; size_t buflen; char ch; bool is_negative = false; bool first_digit = false; if (!peek_first_meaningful_char(buffer, ch)) return false; if (ch == '-') { is_negative = true; buffer->buffer->seek(1); } intv = 0; while (buflen = buffer->buffer->peek(&buf), buf && buflen > 0) { size_t i = 0; char *base = (char *)buf; for (i = 0; i < buflen; i++) { if (!first_digit) { if (isdigit(base[i])) first_digit = true; else return false; } if (!isdigit(base[i])) { buffer->buffer->seek(i); if (is_negative) intv *= -1; return true; } intv *= 10; intv += base[i] - '0'; } buffer->buffer->seek(buflen); } if (!first_digit) return false; if (is_negative) intv *= -1; return true; } bool ThriftJsonUtil::read_uint64(ThriftBuffer *buffer, uint64_t& intv) { const void *buf; size_t buflen; char ch; bool first_digit = false; if (!peek_first_meaningful_char(buffer, ch)) return false; if (!isdigit(ch)) return false; intv = 0; while (buflen = buffer->buffer->peek(&buf), buf && buflen > 0) { size_t i = 0; char *base = (char *)buf; for (i = 0; i < buflen; i++) { if (!first_digit) { if (isdigit(base[i])) first_digit = true; else return false; } if (!isdigit(base[i])) { buffer->buffer->seek(i); return true; } intv *= 10; intv += base[i] - '0'; } buffer->buffer->seek(buflen); } if (!first_digit) return false; return true; } bool ThriftJsonUtil::read_double(ThriftBuffer *buffer, double& d) { if (!skip_whitespace(buffer)) return false; const void *buf; size_t buflen; std::string str; while (buflen = buffer->buffer->peek(&buf), buf && buflen > 0) { size_t i = 0; char *base = (char *)buf; for (i = 0; i < buflen; i++) { if (!isdigit(base[i]) && base[i] != '.' && base[i] !='+' && base[i] !='-' && base[i] !='e' && base[i] !='E') break; } str.append(base, base + i); if (i < buflen) break; buffer->buffer->seek(buflen); } if (str.empty()) return false; char *end; int errno_bak = errno; d = strtod(str.c_str(), &end); if (errno == ERANGE) errno = errno_bak; if (end == str.c_str() // strtod error || end < str.c_str() // should never happend || end > str.c_str() + str.size()) // should never happend return false; buffer->buffer->seek(str.c_str() + str.size() - end); return true; } static constexpr int THRIFT_JSON_STATE_STRING_STATE_NORMAL = 0; static constexpr int THRIFT_JSON_STATE_STRING_STATE_Q1 = 1; static constexpr int THRIFT_JSON_STATE_STRING_STATE_U4 = 2; static constexpr int THRIFT_JSON_STATE_STRING_STATE_U3 = 3; static constexpr int THRIFT_JSON_STATE_STRING_STATE_U2 = 4; static constexpr int THRIFT_JSON_STATE_STRING_STATE_U1 = 5; bool ThriftJsonUtil::read_string(ThriftBuffer *buffer, std::string *str) { int state = THRIFT_JSON_STATE_STRING_STATE_NORMAL; const void *buf; size_t buflen; int n; if (!skip_character(buffer, '\"')) return false; if (str) str->clear(); while (buflen = buffer->buffer->peek(&buf), buf && buflen > 0) { size_t i = 0; char *base = (char *)buf; while (i < buflen) { unsigned char ch = base[i++]; if (state == THRIFT_JSON_STATE_STRING_STATE_NORMAL) { if (ch == '\"') { if (i > 0) { if (str) str->append(base, base + i - 1); } buffer->buffer->seek(i); return true; } else if (ch == '\\') state = THRIFT_JSON_STATE_STRING_STATE_Q1; else if (ch >= 0 && ch < 32) return false; } else if (state == THRIFT_JSON_STATE_STRING_STATE_Q1) { switch (ch) { case '\"': if (str) *str += (char)0x22; break; case '\\': if (str) *str += (char)0x5C; break; case '/': if (str) *str += (char)0x2F; break; case 'b': if (str) *str += (char)0x08; break; case 'f': if (str) *str += (char)0x0C; break; case 'n': if (str) *str += (char)0x0A; break; case 'r': if (str) *str += (char)0x0D; break; case 't': if (str) *str += (char)0x09; break; case 'u': n = 0; state = THRIFT_JSON_STATE_STRING_STATE_U4; break; default: return false; } state = THRIFT_JSON_STATE_STRING_STATE_NORMAL; } else if (state == THRIFT_JSON_STATE_STRING_STATE_U4 || state == THRIFT_JSON_STATE_STRING_STATE_U3 || state == THRIFT_JSON_STATE_STRING_STATE_U2 || state == THRIFT_JSON_STATE_STRING_STATE_U1) { int x = __hex_int(ch); if (x < 0) return false; n = n * 16 + x; if (state == THRIFT_JSON_STATE_STRING_STATE_U1) { if (n < 0 || n >= 65536) return false; if (n < 128) { if (str) *str += (char)n; } else if (n < 2048) { if (str) { *str += (char)(192 | (n >> 6)); *str += (char)(128 | (n & 0x3F)); } } else { if (str) { *str += (char)(224 | (n >> 12)); *str += (char)(128 | (n & 0XFC0)); *str += (char)(128 | (n & 0x3F)); } } state = THRIFT_JSON_STATE_STRING_STATE_NORMAL; } else if (state == THRIFT_JSON_STATE_STRING_STATE_U2) state = THRIFT_JSON_STATE_STRING_STATE_U1; else if (state == THRIFT_JSON_STATE_STRING_STATE_U3) state = THRIFT_JSON_STATE_STRING_STATE_U2; else if (state == THRIFT_JSON_STATE_STRING_STATE_U4) state = THRIFT_JSON_STATE_STRING_STATE_U3; } else return false; } if (str) str->append(base, base + buflen); buffer->buffer->seek(buflen); } return false; } bool ThriftJsonUtil::skip_one_element(ThriftBuffer *buffer) { char ch; if (!peek_first_meaningful_char(buffer, ch)) return false; if (ch == '{') { while (ch != '}') { buffer->buffer->seek(1); if (!read_string(buffer, nullptr)) return false; if (!skip_character(buffer, ':')) return false; if (!skip_one_element(buffer)) return false; if (!peek_first_meaningful_char(buffer, ch)) return false; if (ch != ',' && ch == '}') return false; } return true; } else if (ch == '[') { while (ch != ']') { buffer->buffer->seek(1); if (!skip_one_element(buffer)) return false; if (!peek_first_meaningful_char(buffer, ch)) return false; if (ch != ',' && ch == ']') return false; } return true; } else if (ch == '\"') return read_string(buffer, nullptr); else if (ch =='t') return skip_simple_string(buffer, "true"); else if (ch == 'f') return skip_simple_string(buffer, "false"); else if (ch == 'n') return skip_simple_string(buffer, "null"); else if (ch =='-' || ch == '.' || ch == 'e' || ch == 'E' || isdigit(ch)) { double d; return read_double(buffer, d); } return false; } bool ThriftJsonUtil::escape_string(const std::string& str, std::string& escape_str) { size_t slen = str.size(); escape_str = '\"'; for (size_t i = 0; i < slen; i++) { unsigned char ch = str[i]; if (ch > 127) { int n = 0; if ((ch >> 5) == 6) { if (i + 1 >= slen) return false; n = ch & 0x1F; ch = str[++i]; if ((ch >> 6) != 2) return false; n += ch & 0x3F; if (n < 0x80) return false; } else if ((ch >> 4) == 14) { if (i + 2 >= slen) return false; n = ch & 0xF; ch = str[++i]; if ((ch >> 6) != 2) return false; n += ch & 0x3F; ch = str[++i]; if ((ch >> 6) != 2) return false; n += ch & 0x3F; if (n < 0x800) return false; } else return false; escape_str += "\\u"; escape_str += __hex_ch(n / 4096); n /= 4096; escape_str += __hex_ch(n / 256); n /= 256; escape_str += __hex_ch(n / 16); n /= 16; escape_str += __hex_ch(n); } else if (ch == 0x22) escape_str += "\\\""; else if (ch == 0x5C) escape_str += "\\\\"; else if (ch == 0x2F) escape_str += "\\/"; else if (ch == 0x08) escape_str += "\\b"; else if (ch == 0x0C) escape_str += "\\f"; else if (ch == 0x0A) escape_str += "\\n"; else if (ch == 0x0D) escape_str += "\\r"; else if (ch == 0x09) escape_str += "\\t"; else if (ch >= 0 && ch < 32) { escape_str += "\\u00"; escape_str += __hex_ch(ch / 16); ch /= 16; escape_str += __hex_ch(ch); } else escape_str += ch; } escape_str += '\"'; return true; } } // end namespace srpc sogou-srpc-ac65014/src/thrift/rpc_thrift_idl.h000066400000000000000000000043271525217424400214100ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #ifndef __RPC_THRIFT_IDL_H__ #define __RPC_THRIFT_IDL_H__ #include #include #include #include #include #include "rpc_thrift_buffer.h" namespace srpc { class ThriftDescriptor { public: int8_t data_type; protected: using ReaderFunctionPTR = bool (*)(ThriftBuffer *, void *); using WriterFunctionPTR = bool (*)(const void *, ThriftBuffer *); virtual ~ThriftDescriptor() { } ThriftDescriptor(): reader(nullptr), writer(nullptr), json_reader(nullptr), json_writer(nullptr) {} ThriftDescriptor(const ReaderFunctionPTR r, const WriterFunctionPTR w, const ReaderFunctionPTR jr, const WriterFunctionPTR jw): reader(r), writer(w), json_reader(jr), json_writer(jw) {} public: const ReaderFunctionPTR reader; const WriterFunctionPTR writer; const ReaderFunctionPTR json_reader; const WriterFunctionPTR json_writer; }; struct struct_element { const ThriftDescriptor *desc; const char *name; ptrdiff_t isset_offset; ptrdiff_t data_offset; int16_t field_id; int8_t required_state; }; template class ThriftElementsImpl { public: static const std::list *get_elements_instance() { static const ThriftElementsImpl kInstance; return &kInstance.elements; } private: ThriftElementsImpl() { T::StaticElementsImpl(&this->elements); } std::list elements; }; class ThriftIDLMessage { public: const ThriftDescriptor *descriptor = nullptr; const std::list *elements = nullptr; std::string debug_string() const { return ""; } virtual ~ThriftIDLMessage() { } }; } // end namespace srpc #include "rpc_thrift_idl.inl" #endif sogou-srpc-ac65014/src/thrift/rpc_thrift_idl.inl000066400000000000000000000704461525217424400217500ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ namespace srpc { class ThriftJsonUtil { public: static bool skip_whitespace(ThriftBuffer *buffer); static bool skip_character(ThriftBuffer *buffer, char ch); static bool peek_first_meaningful_char(ThriftBuffer *buffer, char& ch); static bool skip_simple_string(ThriftBuffer *buffer, const std::string& str); static bool read_int64(ThriftBuffer *buffer, int64_t& intv); static bool read_uint64(ThriftBuffer *buffer, uint64_t& intv); static bool read_double(ThriftBuffer *buffer, double& d); static bool read_string(ThriftBuffer *buffer, std::string *str); static bool skip_one_element(ThriftBuffer *buffer); static bool escape_string(const std::string& str, std::string& escape_str); }; template class ThriftDescriptorImpl : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl kInstance; return &kInstance; } public: static bool read(ThriftBuffer *buffer, void *data); static bool write(const void *data, ThriftBuffer *buffer); static bool read_json(ThriftBuffer *buffer, void *data); static bool write_json(const void *data, ThriftBuffer *buffer); private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl::read, ThriftDescriptorImpl::write, ThriftDescriptorImpl::read_json, ThriftDescriptorImpl::write_json) { this->data_type = DATA; //this->reader = ThriftDescriptorImpl::read; //this->writer = ThriftDescriptorImpl::write; //this->json_reader = ThriftDescriptorImpl::read_json; //this->json_writer = ThriftDescriptorImpl::write_json; } }; template class ThriftDescriptorImpl : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl kInstance; return &kInstance; } static bool read(ThriftBuffer *buffer, void *data) { T *list = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); int8_t field_type; int32_t count; if (!buffer->readI08(field_type)) return false; if (!buffer->readI32(count)) return false; list->resize(count); for (auto& ele : *list) { if (!val_desc->reader(buffer, &ele)) return false; } return true; } static bool write(const void *data, ThriftBuffer *buffer) { const T *list = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); if (!buffer->writeI08(val_desc->data_type)) return false; if (!buffer->writeI32(list->size())) return false; for (const auto& ele : *list) { if (!val_desc->writer(&ele, buffer)) return false; } return true; } static bool read_json(ThriftBuffer *buffer, void *data) { T *list = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; typename T::value_type ele; char ch; if (!ThriftJsonUtil::skip_character(buffer, '[')) return false; while (ThriftJsonUtil::peek_first_meaningful_char(buffer, ch)) { if (ch == ']') { buffer->buffer->seek(1); return true; } if (is_first) is_first = false; else { buffer->buffer->seek(1); if (ch != ',') break; } if (!val_desc->json_reader(buffer, &ele)) break; list->emplace_back(std::move(ele)); } return false; } static bool write_json(const void *data, ThriftBuffer *buffer) { const T *list = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; if (!buffer->writeI08('[')) return false; for (const auto& ele : *list) { if (is_first) is_first = false; else if (!buffer->writeI08(',')) return false; if (!val_desc->json_writer(&ele, buffer)) return false; } return buffer->writeI08(']'); } private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl::read, ThriftDescriptorImpl::write, ThriftDescriptorImpl::read_json, ThriftDescriptorImpl::write_json) { this->data_type = TDT_LIST; //this->reader = ThriftDescriptorImpl::read; //this->writer = ThriftDescriptorImpl::write; //this->json_reader = ThriftDescriptorImpl::read_json; //this->json_writer = ThriftDescriptorImpl::write_json; } }; template class ThriftDescriptorImpl, TDT_LIST, void, VALIMPL> : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl, TDT_LIST, void, VALIMPL> kInstance; return &kInstance; } static bool read(ThriftBuffer *buffer, void *data) { std::vector *list = static_cast *>(data); const auto *val_desc = VALIMPL::get_instance(); int8_t field_type; int32_t count; if (!buffer->readI08(field_type)) return false; if (!buffer->readI32(count)) return false; list->resize(count); for (size_t i = 0; i < list->size(); ++i) { bool ele; if (!val_desc->reader(buffer, &ele)) return false; (*list)[i] = ele; } return true; } static bool write(const void *data, ThriftBuffer *buffer) { const std::vector *list = static_cast *>(data); const auto *val_desc = VALIMPL::get_instance(); if (!buffer->writeI08(val_desc->data_type)) return false; if (!buffer->writeI32(list->size())) return false; for (size_t i = 0; i < list->size(); ++i) { bool ele = (*list)[i]; if (!val_desc->writer(&ele, buffer)) return false; } return true; } static bool read_json(ThriftBuffer *buffer, void *data) { std::vector *list = static_cast *>(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; bool ele; char ch; if (!ThriftJsonUtil::skip_character(buffer, '[')) return false; while (ThriftJsonUtil::peek_first_meaningful_char(buffer, ch)) { if (ch == ']') { buffer->buffer->seek(1); return true; } if (is_first) is_first = false; else { buffer->buffer->seek(1); if (ch != ',') break; } if (!val_desc->json_reader(buffer, &ele)) break; list->push_back(ele); } return false; } static bool write_json(const void *data, ThriftBuffer *buffer) { const std::vector *list = static_cast *>(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; if (!buffer->writeI08('[')) return false; for (size_t i = 0; i < list->size(); ++i) { bool ele = (*list)[i]; if (is_first) is_first = false; else if (!buffer->writeI08(',')) return false; if (!val_desc->json_writer(&ele, buffer)) return false; } return buffer->writeI08(']'); } private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl, TDT_LIST, void, VALIMPL>::read, ThriftDescriptorImpl, TDT_LIST, void, VALIMPL>::write, ThriftDescriptorImpl, TDT_LIST, void, VALIMPL>::read_json, ThriftDescriptorImpl, TDT_LIST, void, VALIMPL>::write_json) { this->data_type = TDT_LIST; } }; template class ThriftDescriptorImpl : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl kInstance; return &kInstance; } static bool read(ThriftBuffer *buffer, void *data) { T *set = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); int8_t field_type; int32_t count; typename T::value_type ele; if (!buffer->readI08(field_type)) return false; if (!buffer->readI32(count)) return false; set->clear(); for (int i = 0; i < count; i++) { if (!val_desc->reader(buffer, &ele)) return false; set->insert(std::move(ele)); } return true; } static bool write(const void *data, ThriftBuffer *buffer) { const T *set = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); if (!buffer->writeI08(val_desc->data_type)) return false; if (!buffer->writeI32(set->size())) return false; for (const auto& ele : *set) { if (!val_desc->writer(&ele, buffer)) return false; } return true; } static bool read_json(ThriftBuffer *buffer, void *data) { T *set = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; typename T::value_type ele; char ch; if (!ThriftJsonUtil::skip_character(buffer, '[')) return false; while (ThriftJsonUtil::peek_first_meaningful_char(buffer, ch)) { if (ch == ']') { buffer->buffer->seek(1); return true; } if (is_first) is_first = false; else { buffer->buffer->seek(1); if (ch != ',') break; } if (!val_desc->json_reader(buffer, &ele)) break; set->insert(std::move(ele)); } return false; } static bool write_json(const void *data, ThriftBuffer *buffer) { const T *set = static_cast(data); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; if (!buffer->writeI08('[')) return false; for (const auto& ele : *set) { if (is_first) is_first = false; else if (!buffer->writeI08(',')) return false; if (!val_desc->json_writer(&ele, buffer)) return false; } return buffer->writeI08(']'); } private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl::read, ThriftDescriptorImpl::write, ThriftDescriptorImpl::read_json, ThriftDescriptorImpl::write_json) { this->data_type = TDT_SET; //this->reader = ThriftDescriptorImpl::read; //this->writer = ThriftDescriptorImpl::write; //this->json_reader = ThriftDescriptorImpl::read_json; //this->json_writer = ThriftDescriptorImpl::write_json; } }; template class ThriftDescriptorImpl : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl kInstance; return &kInstance; } static bool read(ThriftBuffer *buffer, void *data) { T *map = static_cast(data); const auto *key_desc = KEYIMPL::get_instance(); const auto *val_desc = VALIMPL::get_instance(); int8_t field_type; int32_t count; typename T::key_type key; typename T::mapped_type val; if (!buffer->readI08(field_type)) return false; if (!buffer->readI08(field_type)) return false; if (!buffer->readI32(count)) return false; map->clear(); for (int i = 0; i < count; i++) { if (!key_desc->reader(buffer, &key)) return false; if (!val_desc->reader(buffer, &val)) return false; map->insert(std::make_pair(std::move(key), std::move(val))); } return true; } static bool write(const void *data, ThriftBuffer *buffer) { const T *map = static_cast(data); const auto *key_desc = KEYIMPL::get_instance(); const auto *val_desc = VALIMPL::get_instance(); if (!buffer->writeI08(key_desc->data_type)) return false; if (!buffer->writeI08(val_desc->data_type)) return false; if (!buffer->writeI32(map->size())) return false; for (const auto& kv : *map) { if (!key_desc->writer(&kv.first, buffer)) return false; if (!val_desc->writer(&kv.second, buffer)) return false; } return true; } static bool read_json(ThriftBuffer *buffer, void *data) { T *map = static_cast(data); const auto *key_desc = KEYIMPL::get_instance(); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; typename T::key_type key; typename T::mapped_type val; char ch; if (!ThriftJsonUtil::skip_character(buffer, '[')) return false; while (ThriftJsonUtil::peek_first_meaningful_char(buffer, ch)) { if (ch == ']') { buffer->buffer->seek(1); return true; } if (is_first) is_first = false; else { buffer->buffer->seek(1); if (ch != ',') break; } if (!ThriftJsonUtil::skip_character(buffer, '{')) return false; if (!ThriftJsonUtil::skip_simple_string(buffer, "key")) return false; if (!ThriftJsonUtil::skip_character(buffer, ':')) return false; if (!key_desc->json_reader(buffer, &key)) break; if (!ThriftJsonUtil::skip_character(buffer, ',')) return false; if (!ThriftJsonUtil::skip_simple_string(buffer, "value")) return false; if (!ThriftJsonUtil::skip_character(buffer, ':')) return false; if (!val_desc->json_reader(buffer, &val)) break; ThriftJsonUtil::skip_character(buffer, '}'); map->insert(std::make_pair(std::move(key), std::move(val))); } return false; } static bool write_json(const void *data, ThriftBuffer *buffer) { const T *map = static_cast(data); const auto *key_desc = KEYIMPL::get_instance(); const auto *val_desc = VALIMPL::get_instance(); bool is_first = true; if (!buffer->writeI08('[')) return false; for (const auto& kv : *map) { if (is_first) is_first = false; else if (!buffer->writeI08(',')) return false; if (!buffer->writeStringBody("{\"key\":")) return false; if (!key_desc->json_writer(&kv.first, buffer)) return false; if (!buffer->writeStringBody(",\"value\":")) return false; if (!val_desc->json_writer(&kv.second, buffer)) return false; if (!buffer->writeI08('}')) return false; } return buffer->writeI08(']'); } private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl::read, ThriftDescriptorImpl::write, ThriftDescriptorImpl::read_json, ThriftDescriptorImpl::write_json) { this->data_type = TDT_MAP; //this->reader = ThriftDescriptorImpl::read; //this->writer = ThriftDescriptorImpl::write; //this->json_reader = ThriftDescriptorImpl::read_json; //this->json_writer = ThriftDescriptorImpl::write_json; } }; template class ThriftDescriptorImpl : public ThriftDescriptor { public: static const ThriftDescriptor *get_instance() { static const ThriftDescriptorImpl kInstance; return &kInstance; } public: static bool read(ThriftBuffer *buffer, void *data) { T *st = static_cast(data); char *base = (char *)data; int8_t field_type; int16_t field_id; auto it = st->elements->cbegin(); while (true) { if (!buffer->readFieldBegin(field_type, field_id)) return false; if (field_type == TDT_STOP) return true; while (it != st->elements->cend() && it->field_id < field_id) ++it; if (it != st->elements->cend() && it->field_id == field_id && it->desc->data_type == field_type) { if (it->required_state != THRIFT_STRUCT_FIELD_REQUIRED) *((bool *)(base + it->isset_offset)) = true; if (!it->desc->reader(buffer, base + it->data_offset)) return false; } else { if (!buffer->skip(field_type)) return false; } } return true; } static bool write(const void *data, ThriftBuffer *buffer) { const T *st = static_cast(data); const char *base = (const char *)data; for (const auto& ele : *(st->elements)) { if (ele.required_state != THRIFT_STRUCT_FIELD_OPTIONAL || *((bool *)(base + ele.isset_offset)) == true) { if (!buffer->writeFieldBegin(ele.desc->data_type, ele.field_id)) return false; if (!ele.desc->writer(base + ele.data_offset, buffer)) return false; } } return buffer->writeFieldStop(); } static bool read_json(ThriftBuffer *buffer, void *data) { T *st = static_cast(data); char *base = (char *)data; bool is_first = true; std::string name; char ch; if (!ThriftJsonUtil::skip_character(buffer, '{')) return false; while (ThriftJsonUtil::peek_first_meaningful_char(buffer, ch)) { if (ch == '}') { buffer->buffer->seek(1); return true; } if (is_first) is_first = false; else { buffer->buffer->seek(1); if (ch != ',') break; } if (!ThriftJsonUtil::read_string(buffer, &name)) return false; if (!ThriftJsonUtil::skip_character(buffer, ':')) return false; bool is_find = false; for (const auto& ele : *(st->elements)) { if (name == ele.name) { is_find = true; if (ele.required_state != THRIFT_STRUCT_FIELD_REQUIRED) *((bool *)(base + ele.isset_offset)) = true; if (!ele.desc->json_reader(buffer, base + ele.data_offset)) return false; break; } } if (!is_find) { if (!ThriftJsonUtil::skip_one_element(buffer)) return false; } } return false; } static bool write_json(const void *data, ThriftBuffer *buffer) { const T *st = static_cast(data); const char *base = (const char *)data; bool is_first = true; if (!buffer->writeI08('{')) return false; for (const auto& ele : *(st->elements)) { if (ele.required_state != THRIFT_STRUCT_FIELD_OPTIONAL || *((bool *)(base + ele.isset_offset)) == true) { if (is_first) is_first = false; else if (!buffer->writeI08(',')) return false; if (!buffer->writeStringBody(std::string("\"") + ele.name + "\":")) return false; if (!ele.desc->json_writer(base + ele.data_offset, buffer)) return false; } } return buffer->writeI08('}'); } private: ThriftDescriptorImpl(): ThriftDescriptor(ThriftDescriptorImpl::read, ThriftDescriptorImpl::write, ThriftDescriptorImpl::read_json, ThriftDescriptorImpl::write_json) { this->data_type = TDT_STRUCT; //this->reader = ThriftDescriptorImpl::read; //this->writer = ThriftDescriptorImpl::write; //this->json_reader = ThriftDescriptorImpl::read_json; //this->json_writer = ThriftDescriptorImpl::write_json; } }; template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { int8_t *p = static_cast(data); return buffer->readI08(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { int8_t *p = static_cast(data); return buffer->readI08(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { int16_t *p = static_cast(data); return buffer->readI16(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { int32_t *p = static_cast(data); return buffer->readI32(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { int64_t *p = static_cast(data); return buffer->readI64(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { uint64_t *p = static_cast(data); return buffer->readU64(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { uint64_t *p = static_cast(data); return buffer->readU64(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return buffer->readString(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return buffer->readString(*p); } template<> inline bool ThriftDescriptorImpl::read(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return buffer->readString(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const int8_t *p = static_cast(data); return buffer->writeI08(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const int8_t *p = static_cast(data); return buffer->writeI08(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const int16_t *p = static_cast(data); return buffer->writeI16(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const int32_t *p = static_cast(data); return buffer->writeI32(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const int64_t *p = static_cast(data); return buffer->writeI64(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const uint64_t *p = static_cast(data); return buffer->writeU64(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const uint64_t *p = static_cast(data); return buffer->writeU64(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); return buffer->writeString(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); return buffer->writeString(*p); } template<> inline bool ThriftDescriptorImpl::write(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); return buffer->writeString(*p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { int8_t *p = static_cast(data); int64_t intv; if (!ThriftJsonUtil::read_int64(buffer, intv)) return false; *p = (int8_t)intv; return true; } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { int8_t *p = static_cast(data); int64_t intv; if (!ThriftJsonUtil::read_int64(buffer, intv)) return false; *p = (int8_t)intv; return true; } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { int16_t *p = static_cast(data); int64_t intv; if (!ThriftJsonUtil::read_int64(buffer, intv)) return false; *p = (int16_t)intv; return true; } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { int32_t *p = static_cast(data); int64_t intv; if (!ThriftJsonUtil::read_int64(buffer, intv)) return false; *p = (int32_t)intv; return true; } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { int64_t *p = static_cast(data); return ThriftJsonUtil::read_int64(buffer, *p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { uint64_t *p = static_cast(data); return ThriftJsonUtil::read_uint64(buffer, *p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { double *p = static_cast(data); return ThriftJsonUtil::read_double(buffer, *p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return ThriftJsonUtil::read_string(buffer, p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return ThriftJsonUtil::read_string(buffer, p); } template<> inline bool ThriftDescriptorImpl::read_json(ThriftBuffer *buffer, void *data) { std::string *p = static_cast(data); return ThriftJsonUtil::read_string(buffer, p); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const int8_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const int8_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const int16_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const int32_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const int64_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const uint64_t *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const double *p = static_cast(data); return buffer->writeStringBody(std::to_string(*p)); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); std::string str; if (!ThriftJsonUtil::escape_string(*p, str)) return false; return buffer->writeStringBody(str); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); std::string str; if (!ThriftJsonUtil::escape_string(*p, str)) return false; return buffer->writeStringBody(str); } template<> inline bool ThriftDescriptorImpl::write_json(const void *data, ThriftBuffer *buffer) { const std::string *p = static_cast(data); std::string str; if (!ThriftJsonUtil::escape_string(*p, str)) return false; return buffer->writeStringBody(str); } } // end namespace srpc sogou-srpc-ac65014/src/var/000077500000000000000000000000001525217424400155255ustar00rootroot00000000000000sogou-srpc-ac65014/src/var/CMakeLists.txt000066400000000000000000000002471525217424400202700ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(var) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(SRC rpc_var.cc ) add_library(${PROJECT_NAME} OBJECT ${SRC}) sogou-srpc-ac65014/src/var/ckms_quantiles.h000066400000000000000000000111761525217424400207260ustar00rootroot00000000000000#ifndef __CKMS_QUANTILE_H__ #define __CKMS_QUANTILE_H__ #include #include #include #include #include #include #include #include namespace srpc { struct Quantile { Quantile(double q, double err) { quantile = q; error = err; u = 2.0 * err / (1.0 - q); v = 2.0 * err / q; } double quantile; double error; double u; double v; }; template class CKMSQuantiles { private: struct Item { Item(TYPE value, int lower_delta, int delta); TYPE value; int g; int delta; }; public: CKMSQuantiles(const std::vector *quantiles); CKMSQuantiles& operator= (const CKMSQuantiles& copy); CKMSQuantiles(const CKMSQuantiles& copy); void insert(TYPE value); TYPE get(double q); void reset(); size_t get_count() const { return this->count; } TYPE get_sum() const { return this->sum; } private: double allowable_error(int rank); bool insert_batch(); void compress(); private: const std::vector *quantiles; TYPE sum; size_t count; std::vector sample; std::array buffer; size_t buffer_count; }; ////////// impl template CKMSQuantiles::Item::Item(TYPE val, int lower_del, int del) { value = val; g = lower_del; delta = del; } template CKMSQuantiles::CKMSQuantiles(const std::vector *q) : quantiles(q), buffer() { this->count = 0; this->sum = 0; this->buffer_count = 0; } template CKMSQuantiles::CKMSQuantiles(const CKMSQuantiles& copy) { this->operator= (copy); } template CKMSQuantiles& CKMSQuantiles::operator= (const CKMSQuantiles& copy) { if (this != ©) { this->quantiles = copy.quantiles; this->count = copy.count; this->sum = copy.sum; this->sample = copy.sample; this->buffer = copy.buffer; this->buffer_count = copy.buffer_count; } return *this; } template void CKMSQuantiles::insert(TYPE value) { this->sum += value; this->buffer[this->buffer_count] = value; ++this->buffer_count; if (this->buffer_count == this->buffer.size()) { this->insert_batch(); this->compress(); } } template TYPE CKMSQuantiles::get(double q) { this->insert_batch(); this->compress(); if (this->sample.empty()) return std::numeric_limits::quiet_NaN(); int rank_min = 0; const auto desired = static_cast(q * this->count); const auto bound = desired + (allowable_error(desired) / 2); auto it = this->sample.begin(); decltype(it) prev; auto cur = it++; while (it != this->sample.end()) { prev = cur; cur = it++; rank_min += prev->g; if (rank_min + cur->g + cur->delta > bound) return prev->value; } return this->sample.back().value; } template void CKMSQuantiles::reset() { this->count = 0; this->sum = 0; this->sample.clear(); this->buffer_count = 0; } template double CKMSQuantiles::allowable_error(int rank) { auto size = this->sample.size(); double min_error = size + 1; for (const auto& q : *(this->quantiles)) { double error; if (rank <= q.quantile * size) error = q.u * (size - rank); else error = q.v * rank; if (error < min_error) min_error = error; } return min_error; } template bool CKMSQuantiles::insert_batch() { if (this->buffer_count == 0) return false; std::sort(this->buffer.begin(), this->buffer.begin() + this->buffer_count); size_t start = 0; size_t idx = 0; size_t item = idx++; TYPE v; int delta; if (this->sample.empty()) { this->sample.emplace_back(this->buffer[0], 1, 0); ++start; ++this->count; } for (size_t i = start; i < this->buffer_count; ++i) { v = this->buffer[i]; while (idx < this->sample.size() && this->sample[item].value < v) item = idx++; if (this->sample[item].value > v) --idx; if (idx - 1 == 0 || idx + 1 == this->sample.size()) delta = 0; else delta = static_cast(std::floor(allowable_error(idx + 1))) + 1; this->sample.emplace(this->sample.begin() + idx, v, 1, delta); this->count++; item = idx++; } this->buffer_count = 0; return true; } template void CKMSQuantiles::compress() { if (this->sample.size() < 2) return; size_t idx = 0; size_t prev; size_t next = idx++; while (idx < this->sample.size()) { prev = next; next = idx++; if (this->sample[prev].g + this->sample[next].g + this->sample[next].delta <= allowable_error(idx - 1)) { this->sample[next].g += this->sample[prev].g; this->sample.erase(this->sample.begin() + prev); } } } } // end namespace srpc #endif sogou-srpc-ac65014/src/var/rpc_var.cc000066400000000000000000000407311525217424400174750ustar00rootroot00000000000000/* Copyright (c) 2021 Sogou, Inc. 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. */ #include #include #include #include #include #include "rpc_var.h" namespace srpc { static bool label_to_str(const std::map& labels, std::string& str) { for (auto it = labels.begin(); it != labels.end(); it++) { if (it != labels.begin()) str += ","; //TODO: check label name regex is "[a-zA-Z_:][a-zA-Z0-9_:]*" str += it->first + "=\"" + it->second + "\""; } return true; } GaugeVar *RPCVarFactory::gauge(const std::string& name) { return static_cast(RPCVarFactory::var(name)); } CounterVar *RPCVarFactory::counter(const std::string& name) { return static_cast(RPCVarFactory::var(name)); } HistogramVar *RPCVarFactory::histogram(const std::string& name) { return static_cast(RPCVarFactory::var(name)); } SummaryVar *RPCVarFactory::summary(const std::string& name) { return static_cast(RPCVarFactory::var(name)); } HistogramCounterVar *RPCVarFactory::histogram_counter(const std::string &name) { return static_cast(RPCVarFactory::var(name)); } RPCVar *RPCVarFactory::var(const std::string& name) { RPCVar *var; RPCVar *new_var; RPCVarLocal *local = RPCVarLocal::get_instance(); auto it = local->vars.find(name); if (it != local->vars.end()) return it->second; var = RPCVarGlobal::get_instance()->find(name); if (var) { new_var = var->create(false); local->add(name, new_var); return new_var; } return NULL; } // a~z, A~Z, _ bool RPCVarFactory::check_name_format(const std::string& name) { for (size_t i = 0; i < name.length(); i++) { if ((name.at(i) < 65 || name.at(i) > 90) && (name.at(i) < 97 || name.at(i) > 122) && name.at(i) != 95) return false; } return true; } void RPCVar::format_name() { //TODO: change aaa.bbb AAA.BBB to aaa_bbb } RPCVarLocal::~RPCVarLocal() { RPCVarGlobal *global_var = RPCVarGlobal::get_instance(); global_var->remove(this); global_var->dup(this->vars); for (auto it = this->vars.begin(); it != this->vars.end(); it++) delete it->second; } RPCVarGlobal::~RPCVarGlobal() { this->finished = true; for (auto& local : this->local_vars) delete local; } void RPCVarGlobal::dup(const std::unordered_map& vars) { if (this->finished == true) return; RPCVarLocal *local; this->mutex.lock(); if (this->local_vars.empty()) local = NULL; else local = this->local_vars[0]; this->mutex.unlock(); if (local == NULL) local = new RPCVarLocal(); local->mutex.lock(); std::unordered_map& local_var = local->vars; for (auto it = vars.begin(); it != vars.end(); it++) { if (local_var.find(it->first) == local_var.end()) { local_var.insert(std::make_pair(it->first, it->second->create(true))); } else { local_var[it->first]->reduce(it->second->get_data(), it->second->get_size()); } } local->mutex.unlock(); } void RPCVarGlobal::remove(const RPCVarLocal *var) { this->mutex.lock(); for (size_t i = 0; i < this->local_vars.size(); i++) { if (this->local_vars[i] == var) { for (size_t j = i; j < this->local_vars.size() - 1; j++) this->local_vars[j] = this->local_vars[j + 1]; break; } } this->local_vars.resize(this->local_vars.size() - 1); this->mutex.unlock(); } RPCVar *RPCVarGlobal::find(const std::string& name) { std::unordered_map::iterator it; RPCVarGlobal *global_var = RPCVarGlobal::get_instance(); RPCVar *ret = NULL; RPCVarLocal *local; global_var->mutex.lock(); for (size_t i = 0; i < global_var->local_vars.size() && !ret; i++) { local = global_var->local_vars[i]; local->mutex.lock(); for (it = local->vars.begin(); it != local->vars.end() && !ret; it++) { if (!name.compare(it->second->get_name())) ret = it->second; } local->mutex.unlock(); } global_var->mutex.unlock(); return ret; } ///////////// var impl RPCVar *GaugeVar::create(bool with_data) { GaugeVar *var = new GaugeVar(this->name, this->help); if (with_data) var->data = this->data; return var; } CounterVar::CounterVar(const std::string &name, const std::string &help) : RPCVar(name, help, VAR_COUNTER) { this->sum = 0; } CounterVar::~CounterVar() { for (auto it = this->data.begin(); it != this->data.end(); it++) delete it->second; } // Caution : // make sure local->mutex.lock() before CounterVar::create(true) RPCVar *CounterVar::create(bool with_data) { CounterVar *var = new CounterVar(this->name, this->help); if (with_data) { for (auto it = this->data.begin(); it != this->data.end(); it++) { var->data.insert(std::make_pair(it->first, (GaugeVar *)it->second->create(true))); } var->sum = this->sum; } return var; } // [deprecate] // This cannot guarantee the GaugeVar still exists // because global will counter->reset() and delete the internal GaugeVar GaugeVar *CounterVar::add(const LABEL_MAP& labels) { std::string label_str; GaugeVar *var; if (!label_to_str(labels, label_str)) return NULL; auto it = this->data.find(label_str); if (it == this->data.end()) { var = new GaugeVar(label_str, ""); this->data.insert(std::make_pair(label_str, var)); } else var = it->second; return var; } void CounterVar::increase(const LABEL_MAP& labels) { std::string label_str; GaugeVar *var; if (!label_to_str(labels, label_str)) return; RPCVarLocal *local = RPCVarLocal::get_instance(); local->mutex.lock(); // against reset() and delete GaugeVar auto it = this->data.find(label_str); if (it == this->data.end()) { var = new GaugeVar(label_str, ""); this->data.insert(std::make_pair(label_str, var)); } else var = it->second; var->increase(); local->mutex.unlock(); return; } void CounterVar::increase(const LABEL_MAP& labels, double value) { std::string label_str; GaugeVar *var; if (!label_to_str(labels, label_str)) return; RPCVarLocal *local = RPCVarLocal::get_instance(); local->mutex.lock(); auto it = this->data.find(label_str); if (it == this->data.end()) { var = new GaugeVar(label_str, ""); this->data.insert(std::make_pair(label_str, var)); } else { var = it->second; } double current = var->get(); var->set(current + value); this->sum += value; local->mutex.unlock(); return; } // Caution : // make sure local->mutex.lock() before CounterVar::reduce() bool CounterVar::reduce(const void *ptr, size_t) { const CounterVar *counter = (const CounterVar *)ptr; const std::unordered_map *data; data = counter->get_map(); for (auto it = data->begin(); it != data->end(); it++) { auto my_it = this->data.find(it->first); if (my_it == this->data.end()) { GaugeVar *var = (GaugeVar *)it->second->create(true); this->data.insert(std::make_pair(it->first, var)); } else my_it->second->reduce(it->second->get_data(), it->second->get_size()); } this->sum += counter->get_sum(); return true; } void CounterVar::collect(RPCVarCollector *collector) { for (auto it = this->data.begin(); it != this->data.end(); it++) collector->collect_counter_each(this, it->first, it->second->get()); } void CounterVar::reset() { for (auto it = this->data.begin(); it != this->data.end(); it++) delete it->second; this->data.clear(); } void HistogramVar::observe(double value) { size_t i = 0; for (; i < this->bucket_boundaries.size(); i++) { if (value <= this->bucket_boundaries[i]) break; } this->bucket_counts[i]++; this->sum += value; this->count++; } HistogramVar::HistogramVar(const std::string& name, const std::string& help, const std::vector& bucket) : RPCVar(name, help, VAR_HISTOGRAM), bucket_boundaries(bucket), bucket_counts(bucket.size() + 1) { this->sum = 0; this->count = 0; } RPCVar *HistogramVar::create(bool with_data) { HistogramVar *var = new HistogramVar(this->name, this->help, this->bucket_boundaries); if (with_data) { var->bucket_counts = this->bucket_counts; var->sum = this->sum; var->count = this->count; } return var; } bool HistogramVar::observe_multi(const std::vector& multi, double sum) { if (multi.size() != this->bucket_counts.size()) return false; for (size_t i = 0; i < multi.size(); i ++) { this->bucket_counts[i] += multi[i]; this->count += multi[i]; } this->sum += sum; return true; } bool HistogramVar::reduce(const void *ptr, size_t sz) { if (sz != this->bucket_boundaries.size() + 1) return false; const HistogramVar *data = (const HistogramVar *)ptr; const std::vector *src_bucket_counts = data->get_bucket_counts(); for (size_t i = 0; i < sz; i++) this->bucket_counts[i] += (*src_bucket_counts)[i]; this->sum += data->get_sum(); this->count += data->get_count(); return true; } void HistogramVar::collect(RPCVarCollector *collector) { size_t i = 0; size_t current = 0; collector->collect_histogram_begin(this); for (; i < this->bucket_boundaries.size(); i++) { // current += this->bucket_counts[i]; current = this->bucket_counts[i]; collector->collect_histogram_each(this, this->bucket_boundaries[i], current); } // current += this->bucket_counts[i]; current = this->bucket_counts[i]; collector->collect_histogram_each(this, DBL_MAX, current); collector->collect_histogram_end(this, this->sum, this->count); } void HistogramVar::reset() { for (size_t i = 0; i < this->bucket_boundaries.size(); i++) this->bucket_counts[i] = 0; this->sum = 0; this->count = 0; } SummaryVar::SummaryVar(const std::string& name, const std::string& help, const std::vector& quantile, const std::chrono::milliseconds max_age, int age_bucket) : RPCVar(name, help, VAR_SUMMARY), quantiles(quantile), quantile_values(&this->quantiles, max_age, age_bucket) { this->sum = 0; this->count = 0; this->max_age = max_age; this->age_buckets = age_bucket; this->quantile_size = this->quantiles.size(); // for output if (this->quantiles[this->quantile_size - 1].quantile != 1.0) { struct Quantile q(1.0, 0.1); this->quantiles.push_back(q); } this->quantile_out.resize(this->quantiles.size(), 0); } RPCVar *SummaryVar::create(bool with_data) { SummaryVar *var = new SummaryVar(this->name, this->help, this->quantiles, this->max_age, this->age_buckets); if (with_data) { var->sum = this->sum; var->count = this->count; var->quantile_values = this->quantile_values; var->quantile_size = this->quantile_size; var->quantile_out = this->quantile_out; } return var; } void SummaryVar::observe(double value) { RPCVarLocal *local = RPCVarLocal::get_instance(); local->mutex.lock(); this->quantile_values.insert(value); local->mutex.unlock(); } bool SummaryVar::reduce(const void *ptr, size_t sz) { if (sz != this->quantiles.size()) return false; SummaryVar *data = (SummaryVar *)ptr; TimeWindowQuantiles *src = data->get_quantile_values(); double get_val; size_t src_count = 0; double *src_value = new double[sz](); double src_sum = src->get_sum(); for (size_t i = 0; i < sz; i++) { src_count = src->get(this->quantiles[i].quantile, &get_val); src_value[i] = get_val; } double pilot; size_t cnt; size_t idx; double range; double count = 0; double value = 0; size_t src_idx = 0; size_t dst_idx = 0; size_t total = this->count + src_count; double *out = new double[sz](); for (size_t i = 0; i < sz; i++) { pilot = this->quantiles[i].quantile * total; while (count < pilot && src_idx < sz && dst_idx < sz) { if (this->quantile_out[dst_idx] <= src_value[src_idx]) { value = this->quantile_out[dst_idx]; idx = dst_idx; cnt = this->count; dst_idx++; } else { value = src_value[src_idx]; idx = src_idx; cnt = src_count; src_idx++; } if (idx == 0) range = this->quantiles[0].quantile; else range = this->quantiles[idx].quantile - this->quantiles[idx - 1].quantile; count += cnt * range; } if (count >= pilot) out[i] = value; else if (src_idx < sz) out[i] = src_value[i]; else out[i] = this->quantile_out[i]; } for (size_t i = 0; i < sz; i++) this->quantile_out[i] = out[i]; this->count = total; this->sum += src_sum; delete[] out; delete[] src_value; return true; } void SummaryVar::collect(RPCVarCollector *collector) { collector->collect_summary_begin(this); for (size_t i = 0; i < this->quantile_size; i++) { collector->collect_summary_each(this, this->quantiles[i].quantile, this->quantile_out[i]); } collector->collect_summary_end(this, this->sum, this->count); this->quantile_out.clear(); } TimedGaugeVar::TimedGaugeVar(const std::string& name, const std::string& help, Clock::duration duration, size_t bucket_num) : GaugeVar(name, help), RPCTimeWindow(duration, bucket_num) { for (size_t i = 0; i < bucket_num; i ++) this->data_bucket.push_back(GaugeVar(name, help)); } void TimedGaugeVar::increase() { this->rotate(); for (auto& bucket : this->data_bucket) bucket.increase(); } void TimedGaugeVar::set(double val) { this->rotate(); for (auto &bucket : this->data_bucket) bucket.set(val); } double TimedGaugeVar::get() { GaugeVar &bucket = this->rotate(); return bucket.get(); } const void *TimedGaugeVar::get_data() { GaugeVar& bucket = this->rotate(); return bucket.get_data(); } RPCVar *TimedGaugeVar::create(bool with_data) { GaugeVar& current_bucket = this->rotate(); RPCVar *var; if (with_data) // for reduce { GaugeVar *gauge = new GaugeVar(this->get_name(), this->get_help()); gauge->set(*(double *)current_bucket.get_data()); var = gauge; } else // for dup into other threads { TimedGaugeVar *timed_gauge = new TimedGaugeVar(this->get_name(), this->get_help(), this->duration, this->bucket_num); var = timed_gauge; } return var; } HistogramCounterVar::HistogramCounterVar(const std::string &name, const std::string &help, const std::vector &bucket) : RPCVar(name, help, VAR_HISTOGRAM_COUNTER), bucket(bucket) { } HistogramCounterVar::~HistogramCounterVar() { for (auto it = this->data.begin(); it != this->data.end(); it++) delete it->second; } // Caution : // make sure local->mutex.lock() before HistogramCounterVar::create(true) RPCVar *HistogramCounterVar::create(bool with_data) { HistogramCounterVar *var = new HistogramCounterVar(this->name, this->help, this->bucket); if (with_data) { for (auto it = this->data.begin(); it != this->data.end(); it++) { var->data.insert(std::make_pair(it->first, (HistogramVar *)it->second->create(true))); } } return var; } void HistogramCounterVar::observe(const LABEL_MAP &labels, double value) { std::string label_str; HistogramVar *var; if (!label_to_str(labels, label_str)) return; RPCVarLocal *local = RPCVarLocal::get_instance(); local->mutex.lock(); auto it = this->data.find(label_str); if (it == this->data.end()) { var = new HistogramVar(label_str, "", this->bucket); this->data.insert(std::make_pair(label_str, var)); } else { var = it->second; } var->observe(value); local->mutex.unlock(); return; } // Caution : // make sure local->mutex.lock() before HistogramCounterVar::reduce() bool HistogramCounterVar::reduce(const void *ptr, size_t) { const HistogramCounterVar *counter = (const HistogramCounterVar *)ptr; const std::unordered_map *data; data = counter->get_map(); for (auto it = data->begin(); it != data->end(); it++) { auto my_it = this->data.find(it->first); if (my_it == this->data.end()) { HistogramVar *var = (HistogramVar *)it->second->create(true); this->data.insert(std::make_pair(it->first, var)); } else { my_it->second->reduce(it->second->get_data(), it->second->get_size()); } } return true; } // TODO: deprecated api available for Prometheus. void HistogramCounterVar::collect(RPCVarCollector *collector) { } void HistogramCounterVar::reset() { for (auto it = this->data.begin(); it != this->data.end(); it++) delete it->second; this->data.clear(); } } // end namespace srpc sogou-srpc-ac65014/src/var/rpc_var.h000066400000000000000000000255431525217424400173430ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifndef __RPC_VAR_H__ #define __RPC_VAR_H__ #include #include #include #include #include #include #include #include "time_window_quantiles.h" namespace srpc { class RPCVar; class RPCVarLocal; class GaugeVar; class CounterVar; class HistogramVar; class SummaryVar; class HistogramCounterVar; enum RPCVarType { VAR_GAUGE = 0, VAR_COUNTER = 1, VAR_HISTOGRAM = 2, VAR_SUMMARY = 3, VAR_HISTOGRAM_COUNTER = 4 }; static std::string type_string(RPCVarType type) { switch (type) { case VAR_GAUGE: return "gauge"; case VAR_COUNTER: return "counter"; case VAR_HISTOGRAM: case VAR_HISTOGRAM_COUNTER: return "histogram"; case VAR_SUMMARY: return "summary"; default: break; } return ""; } class RPCVarFactory { public: // thread local api static GaugeVar *gauge(const std::string& name); static CounterVar *counter(const std::string& name); static HistogramVar *histogram(const std::string& name); static SummaryVar *summary(const std::string& name); static HistogramCounterVar *histogram_counter(const std::string &name); static RPCVar *var(const std::string& name); static bool check_name_format(const std::string& name); }; class RPCVarGlobal { public: static RPCVarGlobal *get_instance() { static RPCVarGlobal kInstance; return &kInstance; } void add(RPCVarLocal *local) { this->mutex.lock(); this->local_vars.push_back(local); this->mutex.unlock(); } RPCVar *find(const std::string& name); // following APIs are used when thread exit and its ~RPCVarLocal() // remove a RPCVarLocal from Glolba->local_vars void remove(const RPCVarLocal *var); // duplicate the vars into global existing RPCVarLocal void dup(const std::unordered_map& vars); private: RPCVarGlobal() { this->finished = false; } ~RPCVarGlobal(); public: std::mutex mutex; std::vector local_vars; bool finished; }; class RPCVarLocal { public: static RPCVarLocal *get_instance() { static thread_local RPCVarLocal kInstance; return &kInstance; } void add(std::string name, RPCVar *var) { this->mutex.lock(); const auto it = this->vars.find(name); if (it == this->vars.end()) this->vars.insert(std::make_pair(std::move(name), var)); this->mutex.unlock(); } virtual ~RPCVarLocal(); private: RPCVarLocal() { RPCVarGlobal::get_instance()->add(this); } public: std::mutex mutex; std::unordered_map vars; friend class RPCVarGlobal; }; class RPCVarCollector { public: virtual void collect_gauge(RPCVar *gauge, double data) = 0; virtual void collect_counter_each(RPCVar *counter, const std::string& label, double data) = 0; virtual void collect_histogram_begin(RPCVar *histogram) = 0; virtual void collect_histogram_each(RPCVar *histogram, double bucket_boudary, size_t current_count) = 0; virtual void collect_histogram_end(RPCVar *histogram, double sum, size_t count) = 0; virtual void collect_summary_begin(RPCVar *summary) = 0; virtual void collect_summary_each(RPCVar *summary, double quantile, double quantile_out) = 0; virtual void collect_summary_end(RPCVar *summary, double sum, size_t count) = 0; }; class RPCVar { public: const std::string& get_name() const { return this->name; } const std::string& get_help() const { return this->help; } RPCVarType get_type() const { return this->type; } const std::string get_type_str() const { return type_string(this->type); } virtual RPCVar *create(bool with_data) = 0; virtual bool reduce(const void *ptr, size_t sz) = 0; virtual size_t get_size() const = 0; virtual const void *get_data() = 0; virtual void collect(RPCVarCollector *collector) = 0; virtual void reset() = 0; public: RPCVar(const std::string& name, const std::string& help, RPCVarType type) : name(name), help(help), type(type) { // this->format_name(); } virtual ~RPCVar() {} private: void format_name(); protected: std::string name; std::string help; RPCVarType type; }; class GaugeVar : public RPCVar { public: virtual void increase() { ++this->data; } virtual void decrease() { --this->data; } size_t get_size() const override { return sizeof(double); } const void *get_data() override { return &this->data; } virtual double get() { return this->data; } virtual void set(double var) { this->data = var; } RPCVar *create(bool with_data) override; bool reduce(const void *ptr, size_t sz) override { this->data += *(double *)ptr; // fprintf(stderr, "reduce data=%d *ptr=%d\n", this->data, *(double *)ptr); return true; } void collect(RPCVarCollector *collector) override { collector->collect_gauge(this, this->data); } void reset() override { this->data = 0; } public: GaugeVar(const std::string& name, const std::string& help) : RPCVar(name, help, VAR_GAUGE) { this->data = 0; } private: double data; }; class CounterVar : public RPCVar { public: using LABEL_MAP = std::map; GaugeVar *add(const LABEL_MAP& labels); void increase(const LABEL_MAP& labels); void increase(const LABEL_MAP &labels, double value); RPCVar *create(bool with_data) override; bool reduce(const void *ptr, size_t sz) override; void collect(RPCVarCollector *collector) override; size_t get_size() const override { return this->data.size(); } const void *get_data() override { return this; } const std::unordered_map *get_map() const { return &this->data; } double get_sum() const { return this->sum; } void increase_sum(double value) { this->sum += value; } void reset() override; public: CounterVar(const std::string &name, const std::string &help); virtual ~CounterVar(); private: std::unordered_map data; double sum; }; class HistogramVar : public RPCVar { public: void observe(double value); // multi is the histogram count of each bucket, including +inf bool observe_multi(const std::vector& multi, double sum); RPCVar *create(bool with_data) override; bool reduce(const void *ptr, size_t sz) override; void collect(RPCVarCollector *collector) override; size_t get_size() const override { return this->bucket_counts.size(); } const void *get_data() override { return this; } double get_sum() const { return this->sum; } size_t get_count() const { return this->count; } const std::vector *get_bucket_boundaries() const { return &this->bucket_boundaries; } const std::vector *get_bucket_counts() const { return &this->bucket_counts; } void reset() override; public: HistogramVar(const std::string& name, const std::string& help, const std::vector& bucket); private: std::vector bucket_boundaries; std::vector bucket_counts; double sum; size_t count; }; class SummaryVar : public RPCVar { public: void observe(double value); RPCVar *create(bool with_data) override; bool reduce(const void *ptr, size_t sz) override; void collect(RPCVarCollector *collector) override; size_t get_size() const override { return this->quantiles.size(); } const void *get_data() override { return this; } double get_sum() const { return this->sum; } size_t get_count() const { return this->count; } TimeWindowQuantiles *get_quantile_values() { return &this->quantile_values; } void reset() override { /* TODO: incorrect if reset() by report interval */ } const std::vector& get_quantiles() const { return this->quantiles; } const std::vector& get_quantile_out() const { return this->quantile_out; } // only for clear stack variable after filled into protobuf or out_string void clear_quantile_out() { this->quantile_out.clear(); } public: SummaryVar(const std::string& name, const std::string& help, const std::vector& quantile, const std::chrono::milliseconds max_age, int age_bucket); private: std::vector quantiles; double sum; size_t count; size_t quantile_size; std::chrono::milliseconds max_age; int age_buckets; TimeWindowQuantiles quantile_values; std::vector quantile_out; }; template class RPCTimeWindow { public: using Clock = std::chrono::steady_clock; RPCTimeWindow(Clock::duration duration, size_t bucket_num); protected: VAR& rotate(); protected: std::vector data_bucket; Clock::duration duration; size_t bucket_num; private: size_t current_bucket; Clock::time_point last_rotation; Clock::duration rotation_interval; }; template RPCTimeWindow::RPCTimeWindow(Clock::duration duration, size_t bucket_num) : duration(duration), bucket_num(bucket_num), rotation_interval(duration / bucket_num) { this->current_bucket = 0; this->last_rotation = Clock::now(); } template VAR& RPCTimeWindow::rotate() { auto delta = Clock::now() - this->last_rotation; while (delta > this->rotation_interval) { this->data_bucket[this->current_bucket].reset(); if (++this->current_bucket >= this->data_bucket.size()) this->current_bucket = 0; delta -= this->rotation_interval; this->last_rotation += this->rotation_interval; } return this->data_bucket[this->current_bucket]; } class TimedGaugeVar : public GaugeVar, RPCTimeWindow { public: TimedGaugeVar(const std::string& name, const std::string& help, Clock::duration duration, size_t bucket_num); // for collect void increase() override; double get() override; void set(double var) override; // for reduce const void *get_data() override; RPCVar *create(bool with_data) override; }; class HistogramCounterVar : public RPCVar { public: using LABEL_MAP = std::map; void observe(const LABEL_MAP &labels, double value); RPCVar *create(bool with_data) override; bool reduce(const void *ptr, size_t sz) override; void collect(RPCVarCollector *collector) override; size_t get_size() const override { return this->data.size(); } const void *get_data() override { return this; } const std::unordered_map *get_map() const { return &this->data; } void reset() override; public: HistogramCounterVar(const std::string &name, const std::string &help, const std::vector &bucket); virtual ~HistogramCounterVar(); private: std::unordered_map data; std::vector bucket; }; } // end namespace srpc #endif sogou-srpc-ac65014/src/var/time_window_quantiles.h000066400000000000000000000053571525217424400223220ustar00rootroot00000000000000#ifndef __TIME_WINDOW_QUANTILES_H__ #define __TIME_WINDOW_QUANTILES_H__ #include #include #include #include #include #include "ckms_quantiles.h" namespace srpc { template class TimeWindowQuantiles { public: using Clock = std::chrono::steady_clock; size_t get(double quantile, TYPE *value); void insert(TYPE value); TYPE get_sum() { return this->ckms_quantiles[current_bucket].get_sum(); } private: CKMSQuantiles& rotate(); const std::vector *quantiles; std::vector> ckms_quantiles; size_t current_bucket; Clock::time_point last_rotation; Clock::duration rotation_interval; public: TimeWindowQuantiles(const std::vector *quantiles, Clock::duration max_age_seconds, int age_buckets); TimeWindowQuantiles(const TimeWindowQuantiles& copy); TimeWindowQuantiles& operator= (const TimeWindowQuantiles& copy); }; ////////// impl template TimeWindowQuantiles::TimeWindowQuantiles(const std::vector *q, const Clock::duration max_age, const int age_buckets) : quantiles(q), ckms_quantiles(age_buckets, CKMSQuantiles(this->quantiles)), rotation_interval(max_age / age_buckets) { this->current_bucket = 0; this->last_rotation = Clock::now(); } template TimeWindowQuantiles::TimeWindowQuantiles(const TimeWindowQuantiles& copy) { this->operator= (copy); } template TimeWindowQuantiles& TimeWindowQuantiles::operator= (const TimeWindowQuantiles& copy) { if (this != ©) { this->quantiles = copy.quantiles; this->ckms_quantiles = copy.ckms_quantiles; this->current_bucket = copy.current_bucket; this->last_rotation = copy.last_rotation; this->rotation_interval = copy.rotation_interval; } return *this; } template size_t TimeWindowQuantiles::get(double quantile, TYPE *value) { CKMSQuantiles& current_bucket = this->rotate(); *value = current_bucket.get(quantile); return current_bucket.get_count(); } template void TimeWindowQuantiles::insert(TYPE value) { this->rotate(); for (auto& bucket : this->ckms_quantiles) bucket.insert(value); } template CKMSQuantiles& TimeWindowQuantiles::rotate() { auto delta = Clock::now() - this->last_rotation; while (delta > this->rotation_interval) { this->ckms_quantiles[this->current_bucket].reset(); if (++this->current_bucket >= this->ckms_quantiles.size()) this->current_bucket = 0; delta -= this->rotation_interval; this->last_rotation += this->rotation_interval; } return this->ckms_quantiles[this->current_bucket]; } } // end namespace srpc #endif sogou-srpc-ac65014/srpc-config.cmake.in000066400000000000000000000006001525217424400177630ustar00rootroot00000000000000@PACKAGE_INIT@ set(SRPC_VERSION "@srpc_VERSION@") set_and_check(SRPC_INCLUDE_DIR "@PACKAGE_CONFIG_INC_DIR@") set_and_check(SRPC_LIB_DIR "@PACKAGE_CONFIG_LIB_DIR@") set_and_check(SRPC_BIN_DIR "@PACKAGE_CONFIG_BIN_DIR@") if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/srpc-targets.cmake") include ("${CMAKE_CURRENT_LIST_DIR}/srpc-targets.cmake") endif () check_required_components(srpc) sogou-srpc-ac65014/srpc.bzl000066400000000000000000000034121525217424400156260ustar00rootroot00000000000000""" Rules for building C++ srpc with Bazel. """ load("@rules_cc//cc:defs.bzl", "cc_library") tool_path = ":srpc_generator" def srpc_cc_library( name, srcs, deps = [], type = "proto", out_prefix = "", visibility = None): output_directory = ( ("$(@D)/%s" % (out_prefix)) if len(srcs) > 1 else ("$(@D)") ) proto_output_headers = [ (out_prefix + "%s.srpc.h") % (s.replace(".%s" % type, "").split("/")[-1]) for s in srcs ] thrift_output_headers = [ (out_prefix + "%s.thrift.h") % (s.replace(".%s" % type, "").split("/")[-1]) for s in srcs ] if type == "thrift": output_headers = proto_output_headers + thrift_output_headers gen_proto = "thrift" if type == "proto": output_headers = proto_output_headers gen_proto = "protobuf" genrule_cmd = " ".join([ "SRCS=($(SRCS));", "for f in $${SRCS[@]:0:%s}; do" % len(srcs), "$(location %s)" % (tool_path), " %s " % gen_proto, "$$f", output_directory + ";", "done", ]) srcs_lib = "%s_srcs" % (name) native.genrule( name = srcs_lib, srcs = srcs, outs = output_headers, tools = [tool_path], cmd = genrule_cmd, output_to_bindir = True, message = "Generating srpc files for %s:" % (name), ) runtime_deps = deps + [":srpc"] print(runtime_deps) cc_library( name = name, hdrs = [ ":" + srcs_lib, ], srcs = [ ":" + srcs_lib, ], features = [ "-parse_headers", ], deps = runtime_deps, includes = [], linkstatic = 1, visibility = visibility, ) sogou-srpc-ac65014/test/000077500000000000000000000000001525217424400151255ustar00rootroot00000000000000sogou-srpc-ac65014/test/CMakeLists.txt000066400000000000000000000102221525217424400176620ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type") project(srpc_test LANGUAGES C CXX ) if (NOT "$ENV{LIBRARY_PATH}" STREQUAL "") string(REPLACE ":" ";" LIBRARY_PATH $ENV{LIBRARY_PATH}) set(CMAKE_SYSTEM_LIBRARY_PATH ${LIBRARY_PATH};${CMAKE_SYSTEM_LIBRARY_PATH}) endif () if (NOT "$ENV{CPLUS_INCLUDE_PATH}" STREQUAL "") string(REPLACE ":" ";" INCLUDE_PATH $ENV{CPLUS_INCLUDE_PATH}) set(CMAKE_SYSTEM_INCLUDE_PATH ${INCLUDE_PATH};${CMAKE_SYSTEM_INCLUDE_PATH}) endif () find_package(OpenSSL REQUIRED) find_package(srpc REQUIRED CONFIG HINTS ..) set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "") if (WIN32) find_package(Protobuf CONFIG REQUIRED) else () find_package(Protobuf REQUIRED) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/lz4/lib/lz4.h") set(LZ4_LIB lz4) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/snappy/cmake") set(SNAPPY_LIB snappy) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in") find_package(Workflow REQUIRED CONFIG HINTS ../workflow) endif () include_directories( ${OPENSSL_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Protobuf_INCLUDE_DIR} ${SRPC_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR} ) if (WIN32) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/Debug/srpc_generator.exe) else () get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/srpc_generator) endif () find_program(CMAKE_MEMORYCHECK_COMMAND valgrind) set(memcheck_command ${CMAKE_MEMORYCHECK_COMMAND} ${CMAKE_MEMORYCHECK_COMMAND_OPTIONS} --error-exitcode=1 --leak-check=full --show-leak-kinds=all) add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND}) enable_testing() if (WIN32) find_package(GTest CONFIG REQUIRED) else () find_package(GTest REQUIRED) endif () set(CMAKE_CXX_STANDARD 17) if (WIN32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /Zc:__cplusplus /std:c++17") else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -fno-exceptions") endif () protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS test_pb.proto) add_custom_target( SRPC_GEN ALL COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/test_pb.proto ${PROJECT_SOURCE_DIR} COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/test_thrift.thrift ${PROJECT_SOURCE_DIR} COMMENT "srpc generator..." ) set(GTEST_LIB GTest::GTest GTest::Main) if (APPLE) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES}) find_library(Workflow_LIB workflow HINTS ../workflow/_lib) find_library(Srpc_LIB srpc HINTS ../_lib) set(SRPC_LIB ${Srpc_LIB} ${Workflow_LIB} pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ) elseif (WIN32) set(SRPC_LIB srpc workflow ws2_32 wsock32 OpenSSL::SSL OpenSSL::Crypto protobuf::libprotobuf ZLIB::ZLIB Snappy::snappy ${LZ4_LIBRARY} ) set(GTEST_LIB GTest::gtest GTest::gtest_main) else () if (SNAPPY_INSTALLED) set(SNAPPY_LIB snappy) endif () if (LZ4_INSTALLED) set(LZ4_LIB lz4) endif () set(SRPC_LIB srpc workflow pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ${SNAPPY_LIB} ${LZ4_LIB} ) endif () set(TEST_LIST unittest) set(BASIC_TEST var_unittest) set(ALL_TEST var_unittest unittest) foreach(src ${TEST_LIST}) add_executable(${src} EXCLUDE_FROM_ALL ${src}.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(${src} ${SRPC_LIB} ${GTEST_LIB}) add_test(${src} ${src}) add_dependencies(${src} SRPC_GEN) add_dependencies(check ${src}) endforeach() foreach(src ${BASIC_TEST}) add_executable(${src} EXCLUDE_FROM_ALL ${src}.cc) target_link_libraries(${src} ${SRPC_LIB} ${GTEST_LIB}) add_test(${src} ${src}) add_dependencies(check ${src}) endforeach() if (WIN32) set(memcheck nothing) elseif (NOT ${CMAKE_MEMORYCHECK_COMMAND} STREQUAL "CMAKE_MEMORYCHECK_COMMAND-NOTFOUND") foreach(src ${ALL_TEST}) add_test(${src}-memory-check ${memcheck_command} ./${src}) endforeach() endif () sogou-srpc-ac65014/test/GNUmakefile000066400000000000000000000016411525217424400172010ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) ALL_TARGETS := all check clean MAKE_FILE := Makefile DEFAULT_BUILD_DIR := build.cmake BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi) CMAKE3 := $(shell if which cmake3 ; then echo cmake3; else echo cmake; fi;) .PHONY: $(ALL_TARGETS) all: mkdir -p $(BUILD_DIR) ifeq ($(DEBUG),y) cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR) else ifneq ("${Workflow_DIR}workflow", "workflow") cd $(BUILD_DIR) && $(CMAKE3) -DWorkflow_DIR:STRING=${Workflow_DIR} $(ROOT_DIR) else cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) endif make -C $(BUILD_DIR) -f Makefile check: mkdir -p $(BUILD_DIR) cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) make -C $(BUILD_DIR) check CTEST_OUTPUT_ON_FAILURE=1 clean: ifeq ($(MAKE_FILE), $(wildcard $(MAKE_FILE))) -make -f Makefile clean endif rm -rf $(DEFAULT_BUILD_DIR) sogou-srpc-ac65014/test/test_pb.proto000066400000000000000000000006721525217424400176570ustar00rootroot00000000000000syntax="proto2"; package unit; message AddRequest { required int32 a = 1; required int32 b = 2; }; message AddResponse { required int32 c = 1; }; message SubstrRequest { required string str = 1; required int32 idx = 2; optional int32 length = 3; }; message SubstrResponse { required string str = 1; }; service TestPB { rpc Add(AddRequest) returns (AddResponse); rpc Substr(SubstrRequest) returns (SubstrResponse); }; sogou-srpc-ac65014/test/test_thrift.thrift000066400000000000000000000002131525217424400207020ustar00rootroot00000000000000namespace cpp unit; service TestThrift { i32 add(1:i32 a, 2:i32 b); string substr(1:string str, 2:i32 idx, 3:i32 length); }; sogou-srpc-ac65014/test/unittest.cc000066400000000000000000000150421525217424400173150ustar00rootroot00000000000000/* Copyright (c) 2020 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include "workflow/WFOperator.h" #include "workflow/WFFacilities.h" #include "test_pb.srpc.h" #include "test_thrift.srpc.h" using namespace srpc; using namespace unit; class ForceShutdown { public: ~ForceShutdown() { google::protobuf::ShutdownProtobufLibrary(); } } g_holder; class TestPBServiceImpl : public TestPB::Service { public: void Add(AddRequest *request, AddResponse *response, RPCContext *ctx) override { response->set_c(request->a() + request->b()); } void Substr(SubstrRequest *request, SubstrResponse *response, RPCContext *ctx) override { if (request->has_length()) response->set_str(std::string(request->str(), request->idx(), request->length())); else response->set_str(std::string(request->str(), request->idx())); } }; class TestThriftServiceImpl : public TestThrift::Service { public: int32_t add(const int32_t a, const int32_t b) override { return a + b; } void substr(std::string& _return, const std::string& str, const int32_t idx, const int32_t length) override { if (length < 0) _return = std::string(str, idx); else _return = std::string(str, idx, length); } }; template void test_pb(SERVER& server) { WFFacilities::WaitGroup wg(1); RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; TestPBServiceImpl impl; server.add_service(&impl); EXPECT_TRUE(server.start("127.0.0.1", 9964) == 0) << "server start failed"; client_params.host = "127.0.0.1"; client_params.port = 9964; CLIENT client(&client_params); AddRequest req1; req1.set_a(123); req1.set_b(456); client.Add(&req1, [&client, &wg](AddResponse *response, RPCContext *ctx) { EXPECT_EQ(ctx->get_status_code(), RPCStatusOK); if (ctx->success()) { EXPECT_EQ(response->c(), 123 + 456); SubstrRequest req2; req2.set_str("hello world!"); req2.set_idx(6); client.Substr(&req2, [&wg](SubstrResponse *response, RPCContext *ctx) { EXPECT_EQ(ctx->get_status_code(), RPCStatusOK); EXPECT_TRUE(response->str() == "world!"); wg.done(); }); } else { wg.done(); } }); wg.wait(); AddResponse resp1; RPCSyncContext ctx1; client.Add(&req1, &resp1, &ctx1); EXPECT_EQ(ctx1.success, true); EXPECT_EQ(resp1.c(), 123 + 456); auto fr = client.async_Add(&req1); auto res = fr.get(); EXPECT_EQ(res.second.success, true); EXPECT_EQ(res.first.c(), 123 + 456); server.stop(); } template void test_thrift(SERVER& server) { WFFacilities::WaitGroup wg(1); RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; TestThriftServiceImpl impl; server.add_service(&impl); EXPECT_TRUE(server.start("127.0.0.1", 9965) == 0) << "server start failed"; client_params.host = "127.0.0.1"; client_params.port = 9965; CLIENT client(&client_params); TestThrift::addRequest req1; req1.a = 123; req1.b = 456; client.add(&req1, [&client, &wg](TestThrift::addResponse *response, RPCContext *ctx) { EXPECT_EQ(ctx->get_status_code(), RPCStatusOK); if (ctx->success()) { EXPECT_EQ(response->result, 123 + 456); TestThrift::substrRequest req2; req2.str = "hello world!"; req2.idx = 6; req2.length = -1; client.substr(&req2, [&wg](TestThrift::substrResponse *response, RPCContext *ctx) { EXPECT_EQ(ctx->get_status_code(), RPCStatusOK); EXPECT_TRUE(response->result == "world!"); wg.done(); }); } else { wg.done(); } }); wg.wait(); int32_t c = client.add(123, 456); EXPECT_EQ(client.thrift_last_sync_success(), true); EXPECT_EQ(c, 123 + 456); client.send_add(123, 456); c = client.recv_add(); EXPECT_EQ(client.thrift_last_sync_success(), true); EXPECT_EQ(c, 123 + 456); server.stop(); } TEST(SRPC, unittest) { RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; SRPCServer server(&server_params); test_pb(server); test_thrift(server); } TEST(SRPCHttp, unittest) { RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; SRPCHttpServer server(&server_params); test_pb(server); } TEST(BRPC, unittest) { RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; BRPCServer server(&server_params); test_pb(server); } TEST(Thrift, unittest) { RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; ThriftServer server(&server_params); test_thrift(server); } TEST(ThriftHttp, unittest) { RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; ThriftHttpServer server(&server_params); test_thrift(server); } TEST(SRPC_COMPRESS, unittest) { WFFacilities::WaitGroup wg(1); RPCServerParams server_params = RPC_SERVER_PARAMS_DEFAULT; RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; SRPCServer server(&server_params); TestPBServiceImpl impl; server.add_service(&impl); EXPECT_TRUE(server.start("127.0.0.1", 9964) == 0) << "server start failed"; client_params.host = "127.0.0.1"; client_params.port = 9964; TestPB::SRPCClient client(&client_params); AddRequest req; req.set_a(123); req.set_b(456); auto&& cb = [](AddResponse *response, RPCContext *ctx) { EXPECT_EQ(ctx->get_status_code(), RPCStatusOK); EXPECT_EQ(ctx->success(), true); EXPECT_EQ(response->c(), 123 + 456); }; auto *t1 = client.create_Add_task(cb); auto *t2 = client.create_Add_task(cb); auto *t3 = client.create_Add_task(cb); auto *t4 = client.create_Add_task(cb); t1->set_compress_type(RPCCompressSnappy); t2->set_compress_type(RPCCompressGzip); t3->set_compress_type(RPCCompressZlib); t4->set_compress_type(RPCCompressLz4); t1->serialize_input(&req); t2->serialize_input(&req); t3->serialize_input(&req); t4->serialize_input(&req); auto& par = *t1 * t2 * t3 * t4; par.set_callback([&wg](const ParallelWork *par) { wg.done(); }); par.start(); wg.wait(); server.stop(); } sogou-srpc-ac65014/test/var_unittest.cc000066400000000000000000000044431525217424400201700ustar00rootroot00000000000000/* Copyright (c) 2023 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include #include #include "srpc/rpc_var.h" using namespace srpc; RPCVar *get_and_reduce(std::string&& var_name) { RPCVar *result = NULL; RPCVarGlobal *global_var = RPCVarGlobal::get_instance(); std::unordered_map::iterator it; global_var->mutex.lock(); for (RPCVarLocal *local : global_var->local_vars) { local->mutex.lock(); it = local->vars.find(var_name); if (it != local->vars.end()) { if (result == NULL) result = it->second->create(true); else result->reduce(it->second->get_data(), it->second->get_size()); } local->mutex.unlock(); } global_var->mutex.unlock(); return result; } TEST(var_unittest, GaugeVar) { GaugeVar *req = new GaugeVar("req", "total req"); RPCVarLocal::get_instance()->add("req", req); RPCVarFactory::gauge("req")->increase(); GaugeVar *gauge = (GaugeVar *)get_and_reduce("req"); EXPECT_EQ(gauge->get(), 1.0); delete gauge; } TEST(var_unittest, TimedGaugeVar) { TimedGaugeVar *qps = new TimedGaugeVar("qps", "req per second", std::chrono::seconds(1), 4); RPCVarLocal::get_instance()->add("qps", qps); RPCVarFactory::gauge("qps")->increase(); RPCVarFactory::gauge("qps")->increase(); GaugeVar *gauge = (GaugeVar *)get_and_reduce("qps"); EXPECT_EQ(gauge->get(), 2.0); delete gauge; usleep(500000); RPCVarFactory::gauge("qps")->increase(); gauge = (GaugeVar *)get_and_reduce("qps"); EXPECT_EQ(gauge->get(), 3.0); delete gauge; usleep(500000); gauge = (GaugeVar *)get_and_reduce("qps"); EXPECT_EQ(gauge->get(), 1.0); delete gauge; } sogou-srpc-ac65014/third_party/000077500000000000000000000000001525217424400164775ustar00rootroot00000000000000sogou-srpc-ac65014/third_party/BUILD000066400000000000000000000000001525217424400172470ustar00rootroot00000000000000sogou-srpc-ac65014/third_party/lz4/000077500000000000000000000000001525217424400172105ustar00rootroot00000000000000sogou-srpc-ac65014/third_party/lz4.BUILD000066400000000000000000000005331525217424400177720ustar00rootroot00000000000000cc_library( name = "lz4", srcs = [ 'lib/lz4.c', 'lib/lz4hc.c', 'lib/lz4frame.c', 'lib/xxhash.c', ], hdrs = [ 'lib/lz4.h', 'lib/lz4.c', 'lib/lz4hc.h', 'lib/lz4frame.h', 'lib/xxhash.h', ], includes = ['lib'], visibility = ["//visibility:public"], ) sogou-srpc-ac65014/third_party/snappy/000077500000000000000000000000001525217424400200115ustar00rootroot00000000000000sogou-srpc-ac65014/third_party/snappy.BUILD000066400000000000000000000017451525217424400206010ustar00rootroot00000000000000genrule( name = "snappy_stubs_public_h", srcs = [ "snappy-stubs-public.h.in", ], outs = [ "snappy-stubs-public.h", ], cmd = "sed 's/$${HAVE_SYS_UIO_H_01}/true/g' $(<) | " + "sed 's/$${PROJECT_VERSION_MAJOR}/0/g' | " + "sed 's/$${PROJECT_VERSION_MINOR}/9/g' | " + "sed 's/$${PROJECT_VERSION_PATCH}/2/g' >$(@)", ) cc_library( name = "snappy", srcs = [ "snappy.cc", "snappy-c.cc", "snappy-sinksource.cc", "snappy-stubs-internal.cc", ], hdrs = [ ":snappy_stubs_public_h", "snappy.h", "snappy-c.h", "snappy-internal.h", "snappy-sinksource.h", "snappy-stubs-internal.h", "snappy-stubs-public.h.in", ], copts = [ "-Wno-non-virtual-dtor", "-Wno-unused-variable", "-Wno-implicit-fallthrough", "-Wno-unused-function", ], includes = ["."], visibility = ["//visibility:public"], ) sogou-srpc-ac65014/tools/000077500000000000000000000000001525217424400153065ustar00rootroot00000000000000sogou-srpc-ac65014/tools/CMakeLists.txt000066400000000000000000000012531525217424400200470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(srpc-ctl VERSION 0.10.5 LANGUAGES C CXX ) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_BUILD_TYPE RelWithDebInfo) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) set(generator_code "../src/generator/generator.cc" "../src/generator/parser.cc") set(srpc_ctl_code "srpc_ctl.cc" "srpc_config.cc" "srpc_controller.cc" "srpc_basic_controller.cc" "srpc_rpc_controller.cc" "srpc_proxy_controller.cc") include_directories("../src/") add_executable(srpc ${srpc_ctl_code} ${generator_code}) target_link_libraries(srpc ${LIBRARY_NAME}) sogou-srpc-ac65014/tools/GNUmakefile000066400000000000000000000005541525217424400173640ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BUILD_DIR := build.cmake .PHONY: all clean all: base make -C $(BUILD_DIR) -f Makefile base: mkdir -p $(BUILD_DIR) cd $(BUILD_DIR) && cmake -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR) clean: ifeq ($(BUILD_DIR), $(wildcard $(BUILD_DIR))) make -C $(BUILD_DIR) clean rm -rf $(BUILD_DIR) endif sogou-srpc-ac65014/tools/README.md000066400000000000000000000260201525217424400165650ustar00rootroot00000000000000[中文版入�](README_cn.md) # srpc tools ### Simple generator for building Workflow and SRPC projects. ## 1. COMPILE ``` git clone https://github.com/sogou/srpc cd srpc/tools make ``` ## 2. USAGE ``` ./srpc ``` ``` Description: Simple generator for building Workflow and SRPC projects. Usage: ./srpc [FLAGS] Available Commands: http - create project with both client and server redis - create project with both client and server rpc - create project with both client and server proxy - create proxy for some client and server protocol file - create project with asynchronous file service compute - create project with asynchronous computing service ``` ## 3. START Execute this simple example: ```sh ./srpc http project_name1 ``` And we will get this on the screen, new project is in `./project_name1/`. ``` Success: make project path " ./project_name1/ " done. Commands: cd ./project_name1/ make -j Execute: ./server ./client ``` Let's take a look at the project: ``` cd ./project_name1/ && tree ``` These files are generated. ``` . ├── CMakeLists.txt ├── GNUmakefile ├── client.conf ├── client_main.cc ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   ├── config.h │   └── util.h ├── full.conf ├── server.conf └── server_main.cc 2 directories, 12 files ``` And we can try to make the project accorrding to the suggestions above. ## 4. HTTP COMMAND commands for HTTP: ``` ./srpc http ``` will get the following instructions: ``` Missing: PROJECT_NAME Usage: ./srpc http [FLAGS] Example: ./srpc redis my_http_project Available Flags: -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` ## 5. RPC COMMAND commands for RPCs: ``` ./srpc rpc ``` will get the following instructions: ``` Missing: PROJECT_NAME Usage: ./srpc rpc [FLAGS] Example: ./srpc redis my_rpc_project Available Flags: -r : rpc type [ SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: SRPC) -o : project output path (default: CURRENT_PATH) -s : service name (default: PROJECT_NAME) -i : idl type [ protobuf | thrift ] (default: protobuf) -x : data type [ protobuf | thrift | json ] (default: idl type. json for http) -c : compress type [ gzip | zlib | snappy | lz4 ] (default: no compression) -d : path of dependencies (default: COMPILE_PATH) -f : specify the idl_file to generate codes (default: template/rpc/IDL_FILE) -p : specify the path for idl_file to depend (default: template/rpc/) ``` We can specified our IDL files with the following command: ``` ./srpc rpc rpc_example -f test_proto/test.proto ``` And we can see the infomation when generating files, which is similar to srpc_genrator. ``` Info: srpc generator begin. proto file: [/root/srpc/tools/rpc_example/test.proto] Successfully parse service block [message] : EchoRequest Successfully parse service block [message] : EchoResponse Successfully parse service block [service] : new_test Successfully parse method:Echo req:EchoRequest resp:EchoResponse finish parsing proto file: [/root/srpc/tools/rpc_example/test.proto] [Generator] generate srpc files: /root/srpc/tools/rpc_example/test.srpc.h [Generator Done] [Generator] generate server files: /root/srpc/tools/rpc_example/server_main.cc, client files: /root/srpc/tools/rpc_example/client_main.cc Info: srpc generator done. Success: make project path " /root/srpc/tools/rpc_example/ " done. Commands: cd /root/srpc/tools/rpc_example/ make -j Execute: ./server ./client ``` ## 6. API COMMAND This command is to create an IDL file, which is convenient for us to create rpc project after changing the file. The command is as follows: ``` Missing: FILE_NAME Usage: ./srpc api [FLAGS] Example: ./srpc api my_api Available Flags: -o : file output path (default: CURRENT_PATH) -i : idl type [ protobuf | thrift ] (default: protobuf) ``` Create a file in proto format by default: ``` ./srpc api test ``` ``` Success: Create api file test.proto at path /root/srpc/tools done. Suggestions: Modify the api file as you needed. And make rpc project base on this file with the following command: ./srpc rpc my_rpc_project -f test.proto -p /root/srpc/tools ``` Then we can create an rpc project through the above suggested commands. The rpc project uses the test.proto file to create the generated code. ## 7. REDIS COMMAND commands for REDIS: ``` ./srpc redis ``` will get the following instructions: ``` Missing: PROJECT_NAME Usage: ./srpc redis [FLAGS] Example: ./srpc redis my_redis_project Available Flags: -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` Make a project with the instructions, we can get the simple redis server and client. The client will send a basic command `SET k1 v1`, and the server will reply `OK` for every request. ``` ./server Redis server started, port 6379 redis server get cmd: [SET] from peer address: 127.0.0.1:60665, seq: 0. ``` ``` ./client Redis client state = 0 error = 0 response: OK ``` If there is user name and password for redis server, client may fill them into client.conf: ``` 1 { 2 "client": 3 { 4 "remote_host": "127.0.0.1", 5 "remote_port": 6379, 6 "retry_max": 2, 7 "user_name": "root", 8 "password": "" 9 } 10 } ``` ## 8. PROXY COMMAND commands for PROXY: ``` ./srpc proxy ``` will get the following instructions: ``` Missing: PROJECT_NAME Usage: ./srpc proxy [FLAGS] Example: ./srpc redis my_proxy_project Available Flags: -c : client type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http) -s : server type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http) -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` Let's make a project with diffrent protocol: ``` ./srpc proxy srpc_trpc_proxy_example -c SRPC -s TRPC ``` ``` Success: make project path " ./srpc_trpc_proxy_example/ " done. Commands: cd ./srpc_trpc_proxy_example/ make -j Execute: ./server ./proxy ./client ``` Check the files in directory: ``` cd srpc_trpc_proxy_example && tree ``` ``` . ├── CMakeLists.txt ├── GNUmakefile ├── client.conf ├── client_main.cc ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   └── config.h ├── proxy.conf ├── proxy_main.cc ├── server.conf ├── server_main.cc └── srpc_trpc_proxy_example.proto 2 directories, 13 files ``` Execute `./server` `./proxy` and `./client` on the three sessions respectively, and we will see that the client sends trpc protocol requests "Hello, this is sync request!" and "Hello, this is async request!" to the proxy, and the proxy receives the request and send to server as srpc protocol. SRPC server fill the response "Hi back" and will finnally transfer back to client by proxy. ``` ./server srpc_trpc_proxy_example TRPC server started, port 1412 get req: message: "Hello, this is sync request!" get req. message: "Hello, this is async request!" ``` ``` ./proxy srpc_trpc_proxy_example [SRPC]-[TRPC] proxy start, port 1411 srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1 message: "Hello, this is sync request!" srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1 message: "Hello, this is async request!" ``` ``` ./client sync resp. message: "Hi back" async resp. message: "Hi back" ``` ## 9. FILE COMMAND This is a simple file server. File reading is asynchronous, and the threads for process( ) will not be blocked by file IOs. ``` ./srpc file file_project ``` will get the following infomation: ``` Success: make project path " ./file_project/ " done. Commands: cd ./file_project/ make -j Execute: ./server Try file service: curl localhost:8080/index.html curl -i localhost:8080/a/b/ ``` Check the files in directories: ``` . ├── CMakeLists.txt ├── GNUmakefile ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   └── config.h ├── file_service.cc ├── file_service.h ├── html │   ├── 404.html │   ├── 50x.html │   └── index.html ├── server.conf └── server_main.cc 3 directories, 13 files ``` Also check the `server.conf`, we can see the `root` and `error_page` are added into file service. We can specify the root to find files for this service, and fill the page coresponding to the error codes. ``` 1 { 2 "server": 3 { 4 "port": 8080, 5 "root": "./html/", 6 "error_page" : [ 7 { 8 "error" : [ 404 ], 9 "page" : "404.html" 10 }, 11 { 12 "error" : [ 500, 502, 503, 504], 13 "page" : "50x.html" 14 } 15 ] 16 } 17 } ``` After we execute `make` and run the server with `./server`, we can make requests with `curl`: example 1: read the file `index.html` in root path `./html/`. ``` curl localhost:8080/index.html Hello from workflow and srpc file server! ``` example 2: read the file `/a/b/`. This does't exist, so the error page `404.html` for `404` will be return as we fill them into `server.conf` above. ``` curl -i localhost:8080/a/b/ HTTP/1.1 404 Not Found Server: SRPC HTTP File Server Content-Length: 59 Connection: Keep-Alive This is not the web page you are looking for. ``` The log printed by server: ``` ./server http file service start, port 8080 file service get request: /a/b/ ``` ## 10. COMPUTE COMMAND Next is a simple calculation server, similarly, the calculation will not block the server processing thread. ``` ./srpc compute compute_test ``` Through the above command, we can create a project, which receives the url request as the parameter n, and performs Fibonacci calculation. ``` Success: make project path " compute_test/ " done. Commands: cd compute_test/ make -j Execute: ./server Try compute with n=8: curl localhost:8080/8 ``` Enter the `compute_test/` directory and use `make` to compile and execute `./server` to run. Then we can use curl or browser to enter `localhost:8080/8` to calculate the 8th Fibonacci number. Here take curl as an example: ``` curl localhost:8080/8

0 + 1 = 1.

1 + 1 = 2.

1 + 2 = 3.

2 + 3 = 5.

3 + 5 = 8.

5 + 8 = 13.

The No. 8 Fibonacci number is: 13.

``` We can see the calculation steps inside the server. This computing example of the server uses go_task to encapsulate a function. Welcome to try more computing scheduling. sogou-srpc-ac65014/tools/README_cn.md000066400000000000000000000302231525217424400172450ustar00rootroot00000000000000[English version](README.md) # srpc�工具 ### 一个帮你快速生�Workflow和SRPC项目的脚手架�工具。 ## 1. 编译 先从github上把srpc项目拷�下�,�工具代�在srpc/tools/目录下,执行`make`��编译。 ``` git clone https://github.com/sogou/srpc cd srpc/tools make ``` ## 2. 用法 执行`./srpc`��看到�工具的用法介�: ``` ./srpc ``` ``` Description: Simple generator for building Workflow and SRPC projects. Usage: ./srpc [FLAGS] Available Commands: http - create project with both client and server redis - create project with both client and server rpc - create project with both client and server api - create protobuf or thrift IDL api proxy - create proxy for some client and server protocol file - create project with asynchronous file service compute - create project with asynchronous computing service ``` ## 3. 入门 我们先从最简�的命令开始入门: ```sh ./srpc http project_name1 ``` 然�就�以看到�幕上显示,项目建立在了新目录`project_name1/`中,并且带有编译和执行的�示命令。 ``` Success: make project path project_name1/ done. Commands: cd project_name1/ make -j Execute: ./server ./client ``` 打开目录,我们查看一下有什么文件: ``` cd ./project_name1/ && tree ``` ``` . ├── CMakeLists.txt ├── GNUmakefile ├── client.conf ├── client_main.cc ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   ├── config.h │   └── util.h ├── full.conf ├── server.conf └── server_main.cc 2 directories, 12 files ``` 然�我们就�以根�上�执行`srpc`命令时所看到的指引,编译和执行这个项目。 ## 4. HTTP 创建HTTP项目的用法如下,�以创建http�议的server和client。其中server和client里的示例代�都�以自行改动,�置文件`server.conf`和`client.conf`里也�以指定基本的�置项,cmake编译文件都已�生�好了,整个项目�以直接拿走使用。 ``` ./srpc http ``` ``` Missing: PROJECT_NAME Usage: ./srpc http [FLAGS] Example: ./srpc http my_http_project Available Flags: -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` ## 5. RPC 创建RPC项目的用法如下,包括了多�rpc�议,protobuf或thrift的文件: ``` ./srpc rpc ``` ``` Missing: PROJECT_NAME Usage: ./srpc rpc [FLAGS] Example: ./srpc rpc my_rpc_project Available Flags: -r : rpc type [ SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: SRPC) -o : project output path (default: CURRENT_PATH) -s : service name (default: PROJECT_NAME) -i : idl type [ protobuf | thrift ] (default: protobuf) -x : data type [ protobuf | thrift | json ] (default: idl type. json for http) -c : compress type [ gzip | zlib | snappy | lz4 ] (default: no compression) -d : path of dependencies (default: COMPILE_PATH) -f : specify the idl_file to generate codes (default: template/rpc/IDL_FILE) -p : specify the path for idl_file to depend (default: template/rpc/) ``` 我们通过以下命令,试一下指定�创建的RPC项目所�赖的proto文件: ``` ./srpc rpc rpc_example -f test_proto/test.proto ``` 然�就�以看到一些生�代�多信�,这和原先使用`srpc_genrator`时所看到的是类似的。 ``` Info: srpc generator begin. proto file: [/root/srpc/tools/rpc_example/test.proto] Successfully parse service block [message] : EchoRequest Successfully parse service block [message] : EchoResponse Successfully parse service block [service] : new_test Successfully parse method:Echo req:EchoRequest resp:EchoResponse finish parsing proto file: [/root/srpc/tools/rpc_example/test.proto] [Generator] generate srpc files: /root/srpc/tools/rpc_example/test.srpc.h [Generator Done] [Generator] generate server files: /root/srpc/tools/rpc_example/server_main.cc, client files: /root/srpc/tools/rpc_example/client_main.cc Info: srpc generator done. Success: make project path rpc_example/ done. Commands: cd rpc_example/ make -j Execute: ./server ./client ``` ## 6. API API命令用于简�地创建一个IDL文件,这样方便我们改完文件之��进行rpc项目调查创建。命令如下: ``` Missing: FILE_NAME Usage: ./srpc api [FLAGS] Example: ./srpc api my_api Available Flags: -o : file output path (default: CURRENT_PATH) -i : idl type [ protobuf | thrift ] (default: protobuf) ``` 我们通过以下命令,�以创建一个默认为proto格�的文件: ``` ./srpc api test ``` ``` Success: Create api file test.proto at path /root/srpc/tools done. Suggestions: Modify the api file as you needed. And make rpc project base on this file with the following command: ./srpc rpc my_rpc_project -f test.proto -p /root/srpc/tools ``` 之�就�以通过以上建议的命令创建一个rpc项目了,rpc项目以test.proto文件创建生�代�,使用方法如`RPC`部分介�。 ## 7. REDIS 创建REDIS�议的client和server,命令如下: ``` ./srpc redis ``` will get the following instructions: ``` Missing: PROJECT_NAME Usage: ./srpc redis [FLAGS] Example: ./srpc redis my_redis_project Available Flags: -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` 根�以上指引我们创建了一个项目�,就�以得到最简�的redis server和client。client就简�地实现了��`SET k1 v1`命令,而server无论收到什么都会简�地回�一个`OK`。我们�以用这简�的示例,改造一个�以请求任何redis�议�务的client,也�以构造一个简�的redis�务器。 ``` ./server Redis server start, port 6379 redis server get cmd: [SET] from peer address: 127.0.0.1:60665, seq: 0. ``` ``` ./client Redis client state = 0 error = 0 response: OK ``` 如果client有填写用户�和密�的需求,�以填到`client.conf`中。我们打开这个�置文件看看: ``` 1 { 2 "client": 3 { 4 "remote_host": "127.0.0.1", 5 "remote_port": 6379, 6 "retry_max": 2, 7 "user_name": "root", 8 "password": "" 9 } 10 } ``` ## 8. PROXY 这个命令用于构建一个转��务器,并且还有与其�议相关的server和client。 ``` ./srpc proxy ``` 执行上述命令,我们�以看到proxy命令的指引: ``` Missing: PROJECT_NAME Usage: ./srpc proxy [FLAGS] Example: ./srpc redis my_proxy_project Available Flags: -c : client type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http) -s : server type for proxy [ Http | Redis | SRPC | SRPCHttp | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http) -o : project output path (default: CURRENT_PATH) -d : path of dependencies (default: COMPILE_PATH) ``` 让我们�试一下,创建一个转����议的项目: ``` ./srpc proxy srpc_trpc_proxy_example -c SRPC -s TRPC ``` ``` Success: make project path srpc_trpc_proxy_example/ " done. Commands: cd srpc_trpc_proxy_example/ make -j Execute: ./server ./proxy ./client ``` 查看新创建的项目中有什么文件: ``` cd srpc_trpc_proxy_example && tree ``` ``` . ├── CMakeLists.txt ├── GNUmakefile ├── client.conf ├── client_main.cc ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   └── config.h ├── proxy.conf ├── proxy_main.cc ├── server.conf ├── server_main.cc └── srpc_trpc_proxy_example.proto 2 directories, 13 files ``` 分别在三个终端执行`./server` `./proxy` 和 `./client`,我们�以看到,client��了trpc�议的请求"Hello, this is sync request!" 和一个异步请求Hello, this is async request!"给proxy,而proxy收到之�把请求用srpc�议�给了server。SRPC server填了回�"Hi back"并通过刚�的proxy路线转回给了client,期间转�纯异步,�会阻塞任何线程。 ``` ./server srpc_trpc_proxy_example TRPC server start, port 1412 get req: message: "Hello, this is sync request!" get req. message: "Hello, this is async request!" ``` ``` ./proxy srpc_trpc_proxy_example [SRPC]-[TRPC] proxy start, port 1411 srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1 message: "Hello, this is sync request!" srpc_trpc_proxy_example proxy get request from client. ip : 127.0.0.1 message: "Hello, this is async request!" ``` ``` ./client sync resp. message: "Hi back" async resp. message: "Hi back" ``` ## 9. FILE 这是一个简�的文件�务器,文件读�都是异步的,�会因为读文件阻塞当��务器的处�线程: ``` ./srpc file file_project ``` ``` Success: make project path file_project/ done. Commands: cd ./file_project/ make -j Execute: ./server Try file service: curl localhost:8080/index.html curl -i localhost:8080/a/b/ ``` 我们通过上述命令创建之�,�以看看文件�务器的目录结构如下: ``` . ├── CMakeLists.txt ├── GNUmakefile ├── config │   ├── Json.cc │   ├── Json.h │   ├── config.cc │   └── config.h ├── file_service.cc ├── file_service.h ├── html │   ├── 404.html │   ├── 50x.html │   └── index.html ├── server.conf └── server_main.cc 3 directories, 13 files ``` 打开`server.conf`,就�以看到我们为文件�务器添加的具体�置项:`root`和`error_page`。我们�以通过root去指定打开文件的根目录,以�通过error_page去关�具体的错误�和它们所�返回作body的页��称。 ``` 1 { 2 "server": 3 { 4 "port": 8080, 5 "root": "./html/", 6 "error_page" : [ 7 { 8 "error" : [ 404 ], 9 "page" : "404.html" 10 }, 11 { 12 "error" : [ 500, 502, 503, 504], 13 "page" : "50x.html" 14 } 15 ] 16 } 17 } ``` 我们执行`make`进行编译,然�执行`./server`把文件�务器跑起�,然�用`curl`进行测试: 示例1:在根目录`./html/`下读�文件`index.html`(�使请求localhost:8080,默认也是读index.html)。 ``` curl localhost:8080/index.html Hello from workflow and srpc file server! ``` 示例2:读文件`/a/b/`,这个文件�存在,所以我们根�上��置文件`server.conf`中所指定的,填入`404`错误�会返回页�`404.html`的内容。 ``` curl -i localhost:8080/a/b/ HTTP/1.1 404 Not Found Server: SRPC HTTP File Server Content-Length: 59 Connection: Keep-Alive This is not the web page you are looking for. ``` 以下信�在server端�以看到: ``` ./server http file service start, port 8080 file service get request: /a/b/ ``` ## 10. COMPUTE 接下�是一个简�的计算�务器,��,计算也�会阻塞当��务器的处�线程: ``` ./srpc compute compute_test ``` 通过以上命令,我们�以创建一个�项目,项目默认接收url请求作为�数n,并进行�波那契计算。 ``` Success: make project path compute_test/ done. Commands: cd compute_test/ make -j Execute: ./server Try compute with n=8: curl localhost:8080/8 ``` 进入`compute_test/`目录执行`make`,并执行`./server`�行起�。然�我们就�以使用curl或者�览器输入`localhost:8080/8`,计算第8个�波那契数是多少。这里以`curl`为例: ``` curl localhost:8080/8

0 + 1 = 1.

1 + 1 = 2.

1 + 2 = 3.

2 + 3 = 5.

3 + 5 = 8.

5 + 8 = 13.

The No. 8 Fibonacci number is: 13.

``` 我们看到了server内部的计算步骤。server的计算例å­�使用了go_task去å°�装一个计算函数,欢迎å°�试更多的计算调度。 sogou-srpc-ac65014/tools/srpc_basic_controller.cc000066400000000000000000000342551525217424400222010ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include #include "srpc_controller.h" using DEFAULT_FILES = std::vector; static std::string server_process_codes(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return std::string(R"( fprintf(stderr, "http server get request_uri: %s\n", task->get_req()->get_request_uri()); print_peer_address(task); task->get_resp()->append_output_body("Hello from server!");)"); if (type == PROTOCOL_TYPE_REDIS) return std::string(R"( protocol::RedisRequest *req = task->get_req(); protocol::RedisResponse *resp = task->get_resp(); protocol::RedisValue val; std::string cmd; if (req->parse_success() == false || req->get_command(cmd) == false) return; fprintf(stderr, "redis server get cmd: [%s] from ", cmd.c_str()); print_peer_address(task); val.set_status("OK"); // example: return OK to every requests resp->set_result(val);)"); return std::string("Unknown type"); } static std::string client_redirect_codes(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return std::string(R"( config.redirect_max(),)"); return std::string(""); } static std::string client_task_callback_codes(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return std::string(R"( if (state == WFT_STATE_SUCCESS) // print server response body { const void *body; size_t body_len; task->get_resp()->get_parsed_body(&body, &body_len); fwrite(body, 1, body_len, stdout); fflush(stdout); })"); if (type == PROTOCOL_TYPE_REDIS) return std::string(R"( protocol::RedisResponse *resp = task->get_resp(); protocol::RedisValue val; if (state == WFT_STATE_SUCCESS && resp->parse_success() == true) { resp->get_result(val); fprintf(stderr, "response: %s\n", val.string_value().c_str()); })"); return std::string("Unknown type"); } static std::string client_set_request_codes(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return std::string(R"( protocol::HttpRequest *req = task->get_req(); req->set_request_uri("/client_request"); // will send to server by proxy )"); if (type == PROTOCOL_TYPE_REDIS) return std::string(R"( task->get_req()->set_request("SET", {"k1", "v1"}); )"); return std::string("Unknown type"); } static std::string username_passwd_codes(uint8_t type) { if (type == PROTOCOL_TYPE_REDIS || type == PROTOCOL_TYPE_MYSQL) return std::string(R"(config.client_user_name() + std::string(":") + config.client_password() + std::string("@") +)"); return std::string(""); } static uint8_t get_protocol_type(const struct srpc_config *config, uint8_t type) { if (config->type == COMMAND_HTTP || config->type == COMMAND_COMPUTE || (config->type == COMMAND_PROXY && type == PROTOCOL_TYPE_HTTP)) return PROTOCOL_TYPE_HTTP; if (config->type == COMMAND_REDIS || (config->type == COMMAND_PROXY && type == PROTOCOL_TYPE_REDIS)) return PROTOCOL_TYPE_REDIS; if (config->type == COMMAND_MYSQL || (config->type == COMMAND_PROXY && type == PROTOCOL_TYPE_MYSQL)) return PROTOCOL_TYPE_MYSQL; return PROTOCOL_TYPE_MAX; } static inline uint8_t get_client_protocol_type(const struct srpc_config *config) { return get_protocol_type(config, config->proxy_client_type); } static inline uint8_t get_server_protocol_type(const struct srpc_config *config) { return get_protocol_type(config, config->proxy_server_type); } bool basic_server_config_transform(const std::string& format, FILE *out, const struct srpc_config *config) { unsigned short port; if (get_server_protocol_type(config) == PROTOCOL_TYPE_HTTP) port = 8080; else if (get_server_protocol_type(config) == PROTOCOL_TYPE_REDIS) port = 6379; else if (get_server_protocol_type(config) == PROTOCOL_TYPE_MYSQL) port = 3306; else port = 1412; size_t len = fprintf(out, format.c_str(), port); return len > 0; } bool basic_client_config_transform(const std::string& format, FILE *out, const struct srpc_config *config) { unsigned short port; std::string redirect_code; std::string user_and_passwd; if (get_client_protocol_type(config) == PROTOCOL_TYPE_HTTP) { port = 8080; redirect_code = R"( "redirect_max": 2,)"; } else if (get_client_protocol_type(config) == PROTOCOL_TYPE_REDIS) { port = 6379; user_and_passwd = R"(, "user_name": "root", "password": "")"; } else if (get_client_protocol_type(config) == PROTOCOL_TYPE_MYSQL) port = 3306; else port = 1412; // for proxy if (config->type == COMMAND_PROXY) { if (get_client_protocol_type(config) == PROTOCOL_TYPE_HTTP) port = 8888; else port = port - 1; } size_t len = fprintf(out, format.c_str(), port, redirect_code.c_str(), user_and_passwd.c_str()); return len > 0; } bool basic_server_transform(const std::string& format, FILE *out, const struct srpc_config *config) { uint8_t server_type = get_server_protocol_type(config); const char *type = get_type_string(server_type); size_t len = fprintf(out, format.c_str(), type, type, server_process_codes(server_type).c_str(), type, type); return len > 0; } bool basic_client_transform(const std::string& format, FILE *out, const struct srpc_config *config) { uint8_t client_type = get_client_protocol_type(config); const char *type = get_type_string(client_type); std::string client_lower = type; std::transform(client_lower.begin(), client_lower.end(), client_lower.begin(), ::tolower); std::string client_request_codes; if ((config->type == COMMAND_PROXY && client_type == PROTOCOL_TYPE_HTTP) || config->type == COMMAND_REDIS) client_request_codes = client_set_request_codes(client_type); size_t len = fprintf(out, format.c_str(), type, type, type, client_task_callback_codes(client_type).c_str(), client_lower.c_str(), username_passwd_codes(client_type).c_str(), type, client_lower.c_str(), client_redirect_codes(client_type).c_str(), client_request_codes.c_str()); return len > 0; } static void basic_default_file_initialize(DEFAULT_FILES& files) { struct CommandController::file_info info; info = { "basic/server.conf", "server.conf", basic_server_config_transform }; files.push_back(info); info = { "basic/client.conf", "client.conf", basic_client_config_transform }; files.push_back(info); info = { "basic/server_main.cc", "server_main.cc", basic_server_transform }; files.push_back(info); info = { "basic/client_main.cc", "client_main.cc", basic_client_transform }; files.push_back(info); info = { "common/config.json", "full.conf", nullptr }; files.push_back(info); info = { "common/util.h", "config/util.h", nullptr }; files.push_back(info); info = { "common/CMakeLists.txt", "CMakeLists.txt", common_cmake_transform }; files.push_back(info); info = { "common/GNUmakefile", "GNUmakefile", nullptr }; files.push_back(info); info = { "config/Json.h", "config/Json.h", nullptr }; files.push_back(info); info = { "config/Json.cc", "config/Json.cc", nullptr }; files.push_back(info); info = { "config/config_simple.h", "config/config.h", nullptr }; files.push_back(info); info = { "config/config_simple.cc", "config/config.cc", nullptr }; files.push_back(info); } static bool get_opt_args(int argc, const char **argv, struct srpc_config *config) { int c; while ((c = getopt(argc, (char * const *)argv, "o:t:d:")) >= 0) { switch (c) { case 'o': if (sscanf(optarg, "%s", config->output_path) != 1) return false; break; case 't': if (sscanf(optarg, "%s", config->template_path) != 1) return false; //TODO: break; case 'd': config->specified_depend_path = true; memset(config->depend_path, 0, MAXPATHLEN); if (sscanf(optarg, "%s", config->depend_path) != 1) return false; break; default: printf(COLOR_RED"Error:\n Unknown args : " COLOR_BLUE"%s\n\n" COLOR_OFF, argv[optind - 1]); return false; } } return true; } static bool basic_get_opt(int argc, const char **argv, struct srpc_config *config) { optind = 2; if (get_opt_args(argc, argv, config) == false) return false; if (optind == argc) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } config->project_name = argv[optind]; optind++; if (get_opt_args(argc, argv, config) == false) return false; if (config->project_name == NULL) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } return true; } static void basic_print_usage(const char *name, const char *command) { printf(COLOR_PINK"Usage:\n" COLOR_INFO" %s " COLOR_COMMAND "%s " COLOR_INFO"" COLOR_FLAG " [FLAGS]\n\n" COLOR_PINK"Example:\n" COLOR_PURPLE" %s %s my_%s_project\n\n" COLOR_PINK"Available Flags:\n" COLOR_FLAG" -o : " COLOR_WHITE"project output path (default: CURRENT_PATH)\n" COLOR_FLAG" -d : " COLOR_WHITE"path of dependencies (default: COMPILE_PATH)\n" COLOR_OFF, name, command, name, command, command); } HttpController::HttpController() { this->config.type = COMMAND_HTTP; basic_default_file_initialize(this->default_files); } void HttpController::print_usage(const char *name) const { basic_print_usage(name, "http"); } bool HttpController::get_opt(int argc, const char **argv) { return basic_get_opt(argc, argv, &this->config); } RedisController::RedisController() { this->config.type = COMMAND_REDIS; basic_default_file_initialize(this->default_files); } void RedisController::print_usage(const char *name) const { basic_print_usage(name, "redis"); } bool RedisController::get_opt(int argc, const char **argv) { return basic_get_opt(argc, argv, &this->config); } FileServiceController::FileServiceController() { this->config.type = COMMAND_FILE; struct file_info info; info = { "file/server.conf", "server.conf", nullptr }; this->default_files.push_back(info); info = { "file/server_main.cc", "server_main.cc", nullptr }; this->default_files.push_back(info); info = { "file/file_service.cc", "file_service.cc", nullptr }; this->default_files.push_back(info); info = { "file/file_service.h", "file_service.h", nullptr }; this->default_files.push_back(info); info = { "file/index.html", "html/index.html", nullptr }; this->default_files.push_back(info); info = { "file/404.html", "html/404.html", nullptr }; this->default_files.push_back(info); info = { "file/50x.html", "html/50x.html", nullptr }; this->default_files.push_back(info); info = { "common/CMakeLists.txt", "CMakeLists.txt", common_cmake_transform }; this->default_files.push_back(info); info = { "common/GNUmakefile", "GNUmakefile", nullptr }; this->default_files.push_back(info); info = { "config/Json.h", "config/Json.h", nullptr }; this->default_files.push_back(info); info = { "config/Json.cc", "config/Json.cc", nullptr }; this->default_files.push_back(info); info = { "config/config_simple.h", "config/config.h", nullptr }; this->default_files.push_back(info); info = { "config/config_simple.cc", "config/config.cc", nullptr }; this->default_files.push_back(info); } void FileServiceController::print_usage(const char *name) const { basic_print_usage(name, "file"); } bool FileServiceController::get_opt(int argc, const char **argv) { return basic_get_opt(argc, argv, &this->config); } void FileServiceController::print_success_info() const { printf(COLOR_GREEN"Success:\n make project path " COLOR_BLUE"\" %s \"" COLOR_GREEN "done.\n\n", this->config.output_path); printf(COLOR_PINK"Commands:\n " COLOR_BLUE "cd %s\n make -j\n\n", this->config.output_path); printf(COLOR_PINK"Execute:\n " COLOR_GREEN "./server\n\n"); printf(COLOR_PINK"Try file service:\n"); printf(COLOR_WHITE" curl localhost:8080/index.html\n"); printf(" curl -i localhost:8080/a/b/\n\n" COLOR_OFF); } ComputeController::ComputeController() { this->config.type = COMMAND_COMPUTE; struct CommandController::file_info info; info = { "basic/server.conf", "server.conf", basic_server_config_transform }; this->default_files.push_back(info); info = { "basic/compute_server_main.cc", "server_main.cc", nullptr }; this->default_files.push_back(info); info = { "common/CMakeLists.txt", "CMakeLists.txt", common_cmake_transform }; this->default_files.push_back(info); info = { "common/GNUmakefile", "GNUmakefile", nullptr }; this->default_files.push_back(info); info = { "config/Json.h", "config/Json.h", nullptr }; this->default_files.push_back(info); info = { "config/Json.cc", "config/Json.cc", nullptr }; this->default_files.push_back(info); info = { "config/config_simple.h", "config/config.h", nullptr }; this->default_files.push_back(info); info = { "config/config_simple.cc", "config/config.cc", nullptr }; this->default_files.push_back(info); } void ComputeController::print_usage(const char *name) const { basic_print_usage(name, "compute"); } bool ComputeController::get_opt(int argc, const char **argv) { return basic_get_opt(argc, argv, &this->config); } void ComputeController::print_success_info() const { printf(COLOR_GREEN"Success:\n make project path " COLOR_BLUE"\" %s \"" COLOR_GREEN " done.\n\n", this->config.output_path); printf(COLOR_PINK"Commands:\n " COLOR_BLUE "cd %s\n make -j\n\n", this->config.output_path); printf(COLOR_PINK"Execute:\n " COLOR_GREEN "./server\n\n"); printf(COLOR_PINK"Try compute with n=8:\n"); printf(COLOR_WHITE" curl localhost:8080/8\n\n" COLOR_OFF); } sogou-srpc-ac65014/tools/srpc_config.cc000066400000000000000000000332041525217424400201130ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include "srpc_config.h" std::vector RPC_PROTOC_SKIP_FILES = { "config_simple.h", "config_simple.cc", "rpc.thrift", "client_thrift.cc", "server_thrift.cc" }; std::vector RPC_THRIFT_SKIP_FILES = { "config_simple.h", "config_simple.cc", "rpc.proto", "client_protobuf.cc", "server_protobuf.cc" }; void usage_kafka(int argc, const char *argv[]) { printf("Usage:\n" " %s kafka [FLAGS]\n\n" "Available Flags:\n" " -o : project output path (default: CURRENT_PATH)\n" " -h : kafka broker url (example: kafka://10.160.23.23:9000," "10.123.23.23,kafka://kafka.sogou)\n" " -g : group name\n" " -d : path of dependencies (default: COMPILE_PATH)\n" , argv[0]); } const char *get_type_string(uint8_t type) { switch (type) { case PROTOCOL_TYPE_HTTP: return "Http"; case PROTOCOL_TYPE_REDIS: return "Redis"; case PROTOCOL_TYPE_MYSQL: return "MySQL"; case PROTOCOL_TYPE_KAFKA: return "Kafka"; case PROTOCOL_TYPE_SRPC: return "SRPC"; case PROTOCOL_TYPE_SRPC_HTTP: return "SRPCHttp"; case PROTOCOL_TYPE_BRPC: return "BRPC"; case PROTOCOL_TYPE_THRIFT: return "Thrift"; case PROTOCOL_TYPE_THRIFT_HTTP: return "ThriftHttp"; case PROTOCOL_TYPE_TRPC: return "TRPC"; case PROTOCOL_TYPE_TRPC_HTTP: return "TRPCHttp"; default: return "Unknown type"; } } static int check_file_idl_type(const char *filename) { size_t len = strlen(filename); if (len > 6 && strcmp(filename + len - 6, ".proto") == 0) return IDL_TYPE_PROTOBUF; else if (len > 7 && strcmp(filename + len - 7, ".thrift") == 0) return IDL_TYPE_THRIFT; return IDL_TYPE_MAX; } srpc_config::srpc_config() { project_name = NULL; service_name = NULL; rpc_type = PROTOCOL_TYPE_SRPC; idl_type = IDL_TYPE_DEFAULT; data_type = DATA_TYPE_DEFAULT; compress_type = COMPRESS_TYPE_NONE; specified_depend_path = false; specified_idl_file = NULL; specified_idl_path = NULL; } bool srpc_config::prepare_specified_idl_file() { if (this->specified_idl_file == NULL) { if (this->specified_idl_path != NULL) { printf(COLOR_RED"Error:\n idl_path is specified " "but NO idl_file.\n\n" COLOR_OFF); return false; } return true; } else if (this->service_name != NULL) { printf(COLOR_RED"Error:\n [-s service_name] does NOT take effect " "when idl_file is specified.\n\n" COLOR_OFF); return false; } this->idl_type = check_file_idl_type(this->specified_idl_file); if (this->idl_type == IDL_TYPE_MAX) { printf(COLOR_RED"Error:\n Invalid idl type. file :" COLOR_BLUE " %s\n\n" COLOR_OFF, this->specified_idl_file); return false; } if (access(this->specified_idl_file, F_OK) != 0) { printf(COLOR_RED"Error:\n idl_file" COLOR_BLUE " %s " COLOR_RED "does NOT exist.\n\n" COLOR_OFF, this->specified_idl_file); return false; } if (this->specified_idl_path == NULL) this->specified_idl_path = this->output_path; return true; } const char *srpc_config::rpc_type_string() const { return get_type_string(this->rpc_type); } const char *srpc_config::rpc_compress_string() const { switch (this->compress_type) { case COMPRESS_TYPE_SNAPPY: return "RPCCompressSnappy"; case COMPRESS_TYPE_GZIP: return "RPCCompressGzip"; case COMPRESS_TYPE_ZLIB: return "RPCCompressZlib"; case COMPRESS_TYPE_LZ4: return "RPCCompressLz4"; default: return "Unknown type"; } } const char *srpc_config::rpc_data_string() const { switch (this->data_type) { case DATA_TYPE_PROTOBUF: return "RPCDataProtobuf"; case DATA_TYPE_THRIFT: return "RPCDataThrift"; case DATA_TYPE_JSON: return "RPCDataJson"; default: return "Unknown type"; } } void srpc_config::set_rpc_type(const char *type) { if (strcasecmp(type, "SRPCHttp") == 0) this->rpc_type = PROTOCOL_TYPE_SRPC_HTTP; else if (strcasecmp(type, "SRPC") == 0) this->rpc_type = PROTOCOL_TYPE_SRPC; else if (strcasecmp(type, "BRPC") == 0) this->rpc_type = PROTOCOL_TYPE_BRPC; else if (strcasecmp(type, "ThriftHttp") == 0) this->rpc_type = PROTOCOL_TYPE_THRIFT_HTTP; else if (strcasecmp(type, "Thrift") == 0) this->rpc_type = PROTOCOL_TYPE_THRIFT; else if (strcasecmp(type, "TRPCHttp") == 0) this->rpc_type = PROTOCOL_TYPE_TRPC_HTTP; else if (strcasecmp(type, "TRPC") == 0) this->rpc_type = PROTOCOL_TYPE_TRPC; else this->rpc_type = PROTOCOL_TYPE_MAX; } void srpc_config::set_idl_type(const char *type) { if (strcasecmp(type, "protobuf") == 0) this->idl_type = IDL_TYPE_PROTOBUF; else if (strcasecmp(type, "thrift") == 0) this->idl_type = IDL_TYPE_THRIFT; else this->idl_type = IDL_TYPE_MAX; } void srpc_config::set_data_type(const char *type) { if (strcasecmp(type, "protobuf") == 0) this->data_type = DATA_TYPE_PROTOBUF; else if (strcasecmp(type, "thrift") == 0) this->data_type = DATA_TYPE_THRIFT; else if (strcasecmp(type, "json") == 0) this->data_type = DATA_TYPE_JSON; else this->data_type = DATA_TYPE_MAX; } void srpc_config::set_compress_type(const char *type) { if (strcasecmp(type, "snappy") == 0) this->compress_type = COMPRESS_TYPE_SNAPPY; else if (strcasecmp(type, "gzip") == 0) this->compress_type = COMPRESS_TYPE_GZIP; else if (strcasecmp(type, "zlib") == 0) this->compress_type = COMPRESS_TYPE_ZLIB; else if (strcasecmp(type, "lz4") == 0) this->compress_type = COMPRESS_TYPE_LZ4; else this->compress_type = COMPRESS_TYPE_MAX; } const char *srpc_config::proxy_server_type_string() const { return get_type_string(this->proxy_server_type); } const char *srpc_config::proxy_client_type_string() const { return get_type_string(this->proxy_client_type); } int check_proxy_type(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP || type == PROTOCOL_TYPE_REDIS || type == PROTOCOL_TYPE_MYSQL || type == PROTOCOL_TYPE_KAFKA) return PROXY_BASIC_TYPE; if (type == PROTOCOL_TYPE_SRPC || type == PROTOCOL_TYPE_SRPC_HTTP || type == PROTOCOL_TYPE_BRPC || type == PROTOCOL_TYPE_TRPC || type == PROTOCOL_TYPE_TRPC_HTTP) return PROXY_PROTOBUF_TYPE; if (type == PROTOCOL_TYPE_THRIFT || type == PROTOCOL_TYPE_THRIFT_HTTP) return PROXY_THRIFT_TYPE; return -1; } ControlGenerator::ControlGenerator(const struct srpc_config *config) : Generator(config->idl_type == IDL_TYPE_THRIFT ? true : false), ctl_printer(config->idl_type == IDL_TYPE_THRIFT ? true : false), config(config) { } bool ControlGenerator::generate_client_cpp_file(const idl_info& info, const std::string& idl_file) { this->client_cpp_file = this->config->output_path; this->client_cpp_file += "client_main.cc"; if (this->ctl_printer.open(this->client_cpp_file) == false) return false; this->ctl_printer.print_clt_include(); this->ctl_printer.print_client_file_include(idl_file); for (const auto& desc : info.desc_list) { if (desc.block_type != "service") continue; for (const auto& rpc : desc.rpcs) { this->ctl_printer.print_client_done_method_ctl(info.package_name, desc.block_name, rpc.method_name, rpc.response_name); } } this->ctl_printer.print_client_main_begin(); this->ctl_printer.print_client_load_config(); this->ctl_printer.print_client_params(); int id = 0; for (const auto& desc : info.desc_list) { if (desc.block_type != "service") continue; std::string suffix; if (id != 0) suffix = std::to_string(id); id++; this->ctl_printer.print_client_main_service_ctl(this->config->rpc_type_string(), info.package_name, desc.block_name, suffix); auto rpc_it = desc.rpcs.cbegin(); if (rpc_it != desc.rpcs.cend()) { this->ctl_printer.print_client_main_method_call(info.package_name, desc.block_name, rpc_it->method_name, rpc_it->request_name, suffix); rpc_it++; } } this->ctl_printer.print_client_main_end(); this->ctl_printer.close(); return false; } bool ControlGenerator::generate_server_cpp_file(const idl_info& info, const std::string& idl_file) { this->server_cpp_file = this->config->output_path; this->server_cpp_file += "server_main.cc"; if (this->ctl_printer.open(this->server_cpp_file) == false) return false; this->ctl_printer.print_clt_include(); this->ctl_printer.print_server_file_include(idl_file); for (const auto& desc : info.desc_list) { if (desc.block_type != "service") continue; this->ctl_printer.print_server_class_impl(info.package_name, desc.block_name); for (const auto& rpc : desc.rpcs) { this->ctl_printer.print_server_impl_method(info.package_name, desc.block_name, rpc.method_name, rpc.request_name, rpc.response_name); } this->ctl_printer.print_server_class_impl_end(); } this->ctl_printer.print_server_main_begin(); this->ctl_printer.print_server_load_config(); this->ctl_printer.print_server_construct(this->config->rpc_type_string()); for (const auto& desc : info.desc_list) { if (desc.block_type != "service") continue; this->ctl_printer.print_server_main_method(desc.block_name); } this->ctl_printer.print_server_main_end_ctl(this->config->project_name, this->config->rpc_type_string()); this->ctl_printer.print_server_main_return(); this->ctl_printer.close(); return true; } static std::string ctl_include_format = R"(#include #include "config/config.h" )"; static std::string ctl_load_config_format = R"( // load config srpc::RPCConfig config; if (config.load("./%s") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); )"; static std::string ctl_client_load_params_format = R"( // start client RPCClientParams params = RPC_CLIENT_PARAMS_DEFAULT; params.host = config.client_host(); params.port = config.client_port(); params.transport_type = config.client_transport_type(); params.is_ssl = config.client_is_ssl(); params.url = config.client_url(); params.callee_timeout = config.client_callee_timeout(); params.caller = config.client_caller(); params.task_params.retry_max = config.retry_max(); )"; static std::string ctl_client_main_params_format = R"( %s::%sClient client%s(¶ms); config.load_filter(client%s); )"; static std::string ctl_client_done_protobuf_format = R"( // printf("%%s\n", response->DebugString().c_str()); )"; static std::string ctl_client_done_format = R"( static void %s_done(%s *response, srpc::RPCContext *context) { if (context->success()) { // TODO: fill your logic to set response%s } else printf("%s %s status[%%d] error[%%d] errmsg:%%s\n", context->get_status_code(), context->get_error(), context->get_errmsg()); } )"; static std::string ctl_server_main_end_format = R"( config.load_filter(server); if (server.start(config.server_port()) == 0) { printf("%s %s server started, port %%u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); )"; void ControlGenerator::ControlPrinter::print_clt_include() { fprintf(this->out_file, "%s", ctl_include_format.c_str()); } void ControlGenerator::ControlPrinter::print_client_load_config() { fprintf(this->out_file, ctl_load_config_format.c_str(), "./client.conf"); } void ControlGenerator::ControlPrinter::print_server_load_config() { fprintf(this->out_file, ctl_load_config_format.c_str(), "./server.conf"); } void ControlGenerator::ControlPrinter::print_client_params() { fprintf(this->out_file, "%s", ctl_client_load_params_format.c_str()); // TODO: set client task params } void ControlGenerator::ControlPrinter:: print_client_done_method_ctl(const std::vector& package, const std::string& service, const std::string& method, const std::string& response) { std::string method_lower = method; std::transform(method_lower.begin(), method_lower.end(), method_lower.begin(), ::tolower); std::string resp; const char *set_resp_code = ""; if (this->is_thrift) resp = make_thrift_package_prefix(package, service, response); else resp = make_package_prefix(package, response); if (!this->is_thrift) set_resp_code = ctl_client_done_protobuf_format.c_str(); fprintf(this->out_file, ctl_client_done_format.c_str(), method_lower.c_str(), resp.c_str(), set_resp_code, service.c_str(), method.c_str(), service.c_str(), method.c_str()); } void ControlGenerator::ControlPrinter:: print_client_main_service_ctl(const std::string& type, const std::vector& package, const std::string& service, const std::string& suffix) { std::string base_service = make_package_prefix(package, service); fprintf(this->out_file, ctl_client_main_params_format.c_str(), base_service.c_str(), type.c_str(), suffix.c_str(), suffix.c_str()); } void ControlGenerator::ControlPrinter:: print_server_construct(const char *rpc_type) { fprintf(this->out_file, " %sServer server;\n", rpc_type); } void ControlGenerator::ControlPrinter:: print_server_main_end_ctl(const char *project_name, const char *rpc_type) { fprintf(this->out_file, ctl_server_main_end_format.c_str(), project_name, rpc_type); } sogou-srpc-ac65014/tools/srpc_config.h000066400000000000000000000110121525217424400177460ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifndef __SRPC_CONFIG__ #define __SRPC_CONFIG__ #include #include #ifndef _WIN32 #include #include #else #define MAXPATHLEN 4096 #include #endif #define COLOR_OFF "\033[0m" #define COLOR_WHITE "\033[37;1m" #define COLOR_RED "\033[31;1m" #define COLOR_GREEN "\033[32;1m" #define COLOR_YELLOW "\033[33;1m" #define COLOR_PURPLE "\033[34;1m" #define COLOR_PINK "\033[35;1m" #define COLOR_BLUE "\033[36;1m" #define COLOR_LPURPLE "\033[94;1m" #define COLOR_COMMAND COLOR_BLUE #define COLOR_INFO COLOR_YELLOW #define COLOR_FLAG COLOR_PURPLE struct srpc_config { uint8_t type; const char *project_name; const char *service_name; char template_path[MAXPATHLEN]; char depend_path[MAXPATHLEN]; char output_path[MAXPATHLEN]; // rpc uint8_t rpc_type; uint8_t idl_type; uint8_t data_type; uint8_t compress_type; bool specified_depend_path; const char *specified_idl_file; const char *specified_idl_path; // proxy uint8_t proxy_server_type; uint8_t proxy_client_type; srpc_config(); bool prepare_args(); bool prepare_project_path(); bool prepare_dependencies() const; bool prepare_specified_idl_file(); const char *rpc_type_string() const; const char *rpc_compress_string() const; const char *rpc_data_string() const; void set_rpc_type(const char *optarg); void set_idl_type(const char *optarg); void set_data_type(const char *optarg); void set_compress_type(const char *optarg); const char *proxy_server_type_string() const; const char *proxy_client_type_string() const; }; class ControlGenerator : public Generator { public: bool generate_client_cpp_file(const idl_info& cur_info, const std::string& idl_file_name); bool generate_server_cpp_file(const idl_info& cur_info, const std::string& idl_file_name); ControlGenerator(const struct srpc_config *config); private: class ControlPrinter : public Printer { public: ControlPrinter(bool is_thrift) : Printer(is_thrift) { } void print_clt_include(); void print_server_load_config(); void print_client_load_config(); void print_client_params(); void print_client_done_method_ctl(const std::vector& pkg, const std::string& service, const std::string& method, const std::string& response); void print_client_main_service_ctl(const std::string& type, const std::vector& pkg, const std::string& service, const std::string& suffix); void print_server_construct(const char *rpc_type); void print_server_main_end_ctl(const char *project_name, const char *rpc_type); }; ControlPrinter ctl_printer; const struct srpc_config *config; }; enum { COMMAND_HTTP, COMMAND_REDIS, COMMAND_MYSQL, COMMAND_KAFKA, COMMAND_RPC, COMMAND_API, COMMAND_PROXY, COMMAND_FILE, COMMAND_COMPUTE }; enum { PROTOCOL_TYPE_HTTP = COMMAND_HTTP, PROTOCOL_TYPE_REDIS = COMMAND_REDIS, PROTOCOL_TYPE_MYSQL = COMMAND_MYSQL, PROTOCOL_TYPE_KAFKA = COMMAND_KAFKA, PROTOCOL_TYPE_SRPC = 22, PROTOCOL_TYPE_SRPC_HTTP, PROTOCOL_TYPE_BRPC, PROTOCOL_TYPE_THRIFT, PROTOCOL_TYPE_THRIFT_HTTP, PROTOCOL_TYPE_TRPC, PROTOCOL_TYPE_TRPC_HTTP, PROTOCOL_TYPE_MAX }; enum { IDL_TYPE_DEFAULT, IDL_TYPE_PROTOBUF, IDL_TYPE_THRIFT, IDL_TYPE_MAX }; enum { DATA_TYPE_DEFAULT, DATA_TYPE_PROTOBUF, DATA_TYPE_THRIFT, DATA_TYPE_JSON, DATA_TYPE_MAX }; enum { COMPRESS_TYPE_NONE, COMPRESS_TYPE_SNAPPY, COMPRESS_TYPE_GZIP, COMPRESS_TYPE_ZLIB, COMPRESS_TYPE_LZ4, COMPRESS_TYPE_MAX }; enum { PROXY_BASIC_TYPE = 1, PROXY_PROTOBUF_TYPE = 2, PROXY_THRIFT_TYPE = 3 }; void usage(int argc, const char *argv[]); void usage_http(int argc, const char *argv[]); void usage_db(int argc, const char *argv[], const struct srpc_config *config); void usage_kafka(int argc, const char *argv[]); void usage_rpc(int argc, const char *argv[], const struct srpc_config *config); const char *get_type_string(uint8_t type); int check_proxy_type(uint8_t type); #endif sogou-srpc-ac65014/tools/srpc_controller.cc000066400000000000000000000350711525217424400210350ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "srpc_controller.h" static constexpr const char *DEPENDENCIES_ERROR = R"(Warning: Default dependencies path : %s does not have third_party dependencies. This may cause link error in project : %s Please check or specify srpc path with '-d' Or use the following command to pull srpc: "git clone --recursive https://github.com/sogou/srpc.git" "cd srpc && make" )"; static constexpr const char *CMAKE_PROTOC_CODES = R"( find_program(PROTOC "protoc") if(${PROTOC} STREQUAL "PROTOC-NOTFOUND") message(FATAL_ERROR "Protobuf compiler is missing!") endif () protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${IDL_FILE}))"; int mkdir_p(const char *name, mode_t mode) { int ret = mkdir(name, mode); if (ret == 0 || errno != ENOENT) return ret; size_t len = strlen(name); if (len > MAXPATHLEN) { errno = ENAMETOOLONG; return -1; } char path[MAXPATHLEN + 1] = {}; memcpy(path, name, len); if (name[len - 1] != '/') { path[len] = '/'; len++; } bool has_valid = false; for (int i = 0; i < len; i++) { if (path[i] != '/' && path[i] != '.') // simple check of valid path { has_valid = true; continue; } if (path[i] == '/' && has_valid == true) { path[i] = '\0'; ret = mkdir(path, mode); if (ret != 0 && errno != EEXIST) return ret; path[i] = '/'; has_valid = false; } } return ret; } static const char *get_proxy_rpc_string(const char *type) { if (strcasecmp(type, "SRPCHttp") == 0) return "SRPCHttp"; if (strcasecmp(type, "SRPC") == 0) return "SRPC"; if (strcasecmp(type, "BRPC") == 0) return "BRPC"; if (strcasecmp(type, "ThriftHttp") == 0) return "ThriftHttp"; if (strcasecmp(type, "Thrift") == 0) return "Thrift"; if (strcasecmp(type, "TRPCHttp") == 0) return "TRPCHttp"; if (strcasecmp(type, "TRPC") == 0) return "TRPC"; return "Unknown type"; } // path=/root/a/ // file=b/c/d.txt // make the middle path and return the full file name static std::string make_file_path(const char *path, const std::string& file) { DIR *dir; auto pos = file.find_last_of('/'); std::string file_name; if (pos != std::string::npos) { file_name = file.substr(pos + 1); std::string dir_name = std::string(path) + std::string("/") + file.substr(0, pos + 1); dir = opendir(dir_name.c_str()); if (dir == NULL) { if (mkdir_p(dir_name.c_str(), 0755) != 0) return ""; } else closedir(dir); } file_name = path; file_name += "/"; file_name += file; return file_name; } static std::string rpc_client_file_codes(const struct srpc_config *config) { std::string ret; if (config->compress_type != COMPRESS_TYPE_NONE) { ret += "\tparams.task_params.compress_type = "; ret += config->rpc_compress_string(); ret += ";\n"; } if (config->data_type != DATA_TYPE_DEFAULT) { ret += "\tparams.task_params.data_type = "; ret += config->rpc_data_string(); ret += ";\n"; } return ret; } static std::string rpc_server_file_codes(const struct srpc_config *config) { std::string ret; if (config->compress_type != COMPRESS_TYPE_NONE) { ret += " ctx->set_compress_type("; ret += config->rpc_compress_string(); ret += ");\n"; } if (config->data_type != DATA_TYPE_DEFAULT) { ret += " ctx->set_data_type("; ret += config->rpc_data_string(); ret += ");\n"; } ret += " "; return ret; } bool rpc_idl_transform(const std::string& format, FILE *out, const struct srpc_config *config) { size_t len = fprintf(out, format.c_str(), config->service_name); return len > 0; } bool rpc_client_transform(const std::string& format, FILE *out, const struct srpc_config *config) { std::string prepare_params = rpc_client_file_codes(config); const char *rpc_type; if (config->type == COMMAND_PROXY) rpc_type = get_proxy_rpc_string(config->proxy_client_type_string()); else rpc_type = config->rpc_type_string(); const char *thrift_namespace = ""; if (config->idl_type == IDL_TYPE_THRIFT) thrift_namespace = config->project_name; size_t len = fprintf(out, format.c_str(), config->service_name, prepare_params.c_str(), config->service_name, rpc_type, thrift_namespace, thrift_namespace); return len > 0; } bool rpc_server_transform(const std::string& format, FILE *out, const struct srpc_config *config) { std::string prepare_ctx = rpc_server_file_codes(config); const char *rpc_type; if (config->type == COMMAND_PROXY) rpc_type = get_proxy_rpc_string(config->proxy_server_type_string()); else rpc_type = config->rpc_type_string(); size_t len = fprintf(out, format.c_str(), config->service_name, config->service_name, prepare_ctx.c_str(), rpc_type, config->project_name, rpc_type); return len > 0; } bool CommandController::parse_args(int argc, const char **argv) { if (argc < 3) { if (argc == 2 && strncmp(argv[1], "api", strlen(argv[1])) == 0) printf(COLOR_RED "Missing: FILE_NAME\n\n" COLOR_OFF); else printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } memset(this->config.output_path, 0, MAXPATHLEN); if (get_path(__FILE__, this->config.depend_path, 2) == false) return false; if (get_path(__FILE__, this->config.template_path, 1) == false) return false; snprintf(this->config.template_path + strlen(this->config.template_path), MAXPATHLEN - strlen(this->config.template_path), "templates/"); if (this->get_opt(argc, argv) == false) return false; if (this->check_args() == false) return false; return true; } bool CommandController::check_args() { size_t path_len = strlen(this->config.output_path); if (strlen(this->config.project_name) >= MAXPATHLEN - path_len - 2) { printf(COLOR_RED"Error:\n project name" COLOR_BLUE " %s " COLOR_RED"or path" COLOR_BLUE " %s " COLOR_RED" is too long. Total limit : %d.\n\n" COLOR_OFF, this->config.project_name, this->config.output_path, MAXPATHLEN); return false; } if (path_len != 0 && this->config.output_path[path_len - 1] != '/') { this->config.output_path[path_len] = '/'; path_len++; } snprintf(this->config.output_path + path_len, MAXPATHLEN - path_len, "%s/", this->config.project_name); return true; } static inline bool is_with_srpc(const struct srpc_config *config) { if (config->type == COMMAND_RPC || (config->type == COMMAND_PROXY && check_proxy_type(config->proxy_server_type) != PROXY_BASIC_TYPE && check_proxy_type(config->proxy_client_type) != PROXY_BASIC_TYPE)) { return true; } return false; } static bool check_dependencies_and_init(const struct srpc_config *config) { int status; bool with_srpc = is_with_srpc(config); // the specified_depend_path must for workflow if (config->specified_depend_path == true && with_srpc == false) return true; std::string check_file = config->depend_path; if (with_srpc == false) check_file += "workflow/workflow-config.cmake.in"; else check_file += "third_party/snappy/cmake"; if (access(check_file.c_str(), 0) != 0) { std::string cmd = "cd "; cmd += config->depend_path; cmd += "&& git submodule update --init"; if (with_srpc == false) cmd += " workflow"; status = system(cmd.c_str()); if (status == -1 || status == 127) { perror("git submodule update failed"); return false; } if (access(check_file.c_str(), 0) != 0) { printf(DEPENDENCIES_ERROR, config->depend_path, config->project_name); return false; } } return true; } static bool check_libraries_and_compile(const struct srpc_config *config) { int status; std::string library_path; std::string compile_path = config->depend_path; if (is_with_srpc(config) == false && config->specified_depend_path == false) compile_path += "/workflow"; library_path = compile_path; library_path += "/_lib"; if (access(library_path.c_str(), 0) != 0) { std::string cmd = "cd "; cmd += compile_path; cmd += " && make -j4"; status = system(cmd.c_str()); if (status == -1 || status == 127) { perror("execute command make failed"); return false; } } return true; } bool CommandController::dependencies_and_dir() { struct srpc_config *config = &this->config; DIR *dir; dir = opendir(config->output_path); if (dir != NULL) { printf(COLOR_RED "Error:\n project path " COLOR_BLUE " %s " COLOR_RED " EXISTS.\n\n" COLOR_OFF, config->output_path); closedir(dir); return false; } dir = opendir(config->template_path); if (dir == NULL) { printf(COLOR_RED "Error:\n template path " COLOR_BLUE " %s " COLOR_RED "does NOT exist.\n" COLOR_OFF, config->template_path); return false; } closedir(dir); if (check_dependencies_and_init(config) == false) return false; if (check_libraries_and_compile(config) == false) return false; if (mkdir_p(config->output_path, 0755) != 0) { perror("Error:\n failed to make project "); return false; } std::string config_path = config->output_path; config_path += "/config"; if (mkdir(config_path.c_str(), 0755) != 0) { perror("Error:\n failed to make project config path "); return false; } return true; } // get the depth-th upper path of file bool CommandController::get_path(const char *file, char *path, int depth) { size_t len = strlen(file); size_t i; memset(path, 0, MAXPATHLEN); if (len == 0 || depth <= 0) return false; int state = 0; for (i = len - 1; i >= 0; i--) { if (file[i] == '/') { state++; if (state >= depth) break; } } if (state != depth) return false; memcpy(path, file, i + 1); return true; } bool CommandController::copy_single_file(const std::string& in_file, const std::string& out_file, transform_function_t transform) { FILE *read_fp; FILE *write_fp; size_t size; char *buf; bool ret = false; read_fp = fopen(in_file.c_str(), "r"); if (read_fp) { write_fp = fopen(out_file.c_str(), "w"); if (write_fp) { fseek(read_fp, 0, SEEK_END); size = ftell(read_fp); buf = (char *)malloc(size); if (buf) { fseek(read_fp, 0, SEEK_SET); if (fread(buf, size, 1, read_fp) == 1) { if (transform != nullptr) { std::string format = std::string(buf, size); ret = transform(format, write_fp, &this->config); } else ret = fwrite(buf, size, 1, write_fp) >= 0 ? true : false; } else { printf(COLOR_RED"Error:\n read " COLOR_WHITE " %s " COLOR_RED "failed\n\n" COLOR_OFF, in_file.c_str()); } free(buf); } else printf(COLOR_RED"Error:\n system error.\n\n" COLOR_OFF); fclose(write_fp); } else { printf(COLOR_RED"Error:\n write" COLOR_WHITE " %s " COLOR_RED"failed\n\n" COLOR_OFF, out_file.c_str()); } } else { printf(COLOR_RED"Error:\n open" COLOR_WHITE " %s " COLOR_RED " failed.\n\n" COLOR_OFF, in_file.c_str()); } return ret; } bool CommandController::copy_files() { std::string read_file; std::string write_file; bool ret = true; for (auto it = this->default_files.begin(); it != this->default_files.end() && ret == true; it++) { read_file = this->config.template_path; read_file += it->in_file; write_file = make_file_path(this->config.output_path, it->out_file); if (write_file.empty()) ret = false; else ret = this->copy_single_file(read_file, write_file, it->transform); } return ret; } void CommandController::print_success_info() const { printf(COLOR_GREEN"Success!\n make project path" COLOR_BLUE" %s " COLOR_GREEN " done.\n\n", this->config.output_path); printf(COLOR_PINK"Commands:\n " COLOR_BLUE "cd %s\n make -j\n\n", this->config.output_path); printf(COLOR_PINK"Execute:\n " COLOR_GREEN "./server\n ./client\n\n" COLOR_OFF); } bool common_cmake_transform(const std::string& format, FILE *out, const struct srpc_config *config) { std::string path = config->depend_path; if (config->specified_depend_path == false) path += "workflow"; std::string codes_str; std::string executors_str; if (config->type == COMMAND_FILE) codes_str = " file_service.cc"; if (config->type != COMMAND_FILE && config->type != COMMAND_COMPUTE) executors_str = " client"; if (config->type == COMMAND_PROXY) executors_str += " proxy"; size_t len = fprintf(out, format.c_str(), config->project_name, path.c_str(), codes_str.c_str(), executors_str.c_str()); return len > 0; } void CommandController::fill_rpc_default_files() { struct file_info info; std::string idl_file_name, client_file_name, server_file_name; std::string out_file_name = this->config.project_name; if (this->config.idl_type == IDL_TYPE_PROTOBUF) { out_file_name += ".proto"; idl_file_name = "rpc/rpc.proto"; client_file_name = "rpc/client_protobuf.cc"; server_file_name = "rpc/server_protobuf.cc"; } else { out_file_name += ".thrift"; idl_file_name = "rpc/rpc.thrift"; client_file_name = "rpc/client_thrift.cc"; server_file_name = "rpc/server_thrift.cc"; } info = { idl_file_name , out_file_name, rpc_idl_transform }; this->default_files.push_back(info); info = { client_file_name, "client_main.cc", rpc_client_transform }; this->default_files.push_back(info); info = { server_file_name, "server_main.cc", rpc_server_transform }; this->default_files.push_back(info); info = { "rpc/server.conf", "server.conf", nullptr }; this->default_files.push_back(info); if (this->config.type == COMMAND_RPC) info = { "rpc/client.conf", "client.conf", nullptr }; else info = { "proxy/client_rpc.conf", "client.conf", nullptr }; this->default_files.push_back(info); } bool rpc_cmake_transform(const std::string& format, FILE *out, const struct srpc_config *config) { std::string idl_file_name; std::string srpc_path = config->depend_path; std::string workflow_path = config->depend_path; workflow_path += "workflow"; if (config->specified_idl_file != NULL) { idl_file_name = config->specified_idl_file; size_t pos = idl_file_name.find_last_of('/'); if (pos != std::string::npos) idl_file_name = idl_file_name.substr(pos + 1); } else if (config->idl_type == IDL_TYPE_PROTOBUF) { idl_file_name = config->project_name; idl_file_name += ".proto"; } else { idl_file_name = config->project_name; idl_file_name += ".thrift"; } std::string is_proxy_str = config->type == COMMAND_PROXY ? " proxy" : ""; size_t len = fprintf(out, format.c_str(), config->project_name, workflow_path.c_str(), srpc_path.c_str(), idl_file_name.c_str(), config->idl_type == IDL_TYPE_PROTOBUF ? CMAKE_PROTOC_CODES : "", is_proxy_str.c_str()); return len > 0; } sogou-srpc-ac65014/tools/srpc_controller.h000066400000000000000000000112601525217424400206710ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #ifndef __SRPC_CONTROLLER__ #define __SRPC_CONTROLLER__ #include "srpc_config.h" class CommandController { public: bool parse_args(int argc, const char **argv); virtual bool dependencies_and_dir(); virtual bool copy_files(); virtual void print_success_info() const; virtual void print_usage(const char *name) const = 0; protected: virtual bool check_args(); private: virtual bool get_opt(int argc, const char **argv) = 0; public: CommandController() { } virtual ~CommandController() { } using transform_function_t = bool (*)(const std::string&, FILE *, const struct srpc_config *); struct file_info { std::string in_file; std::string out_file; transform_function_t transform; }; protected: std::vector default_files; struct srpc_config config; protected: bool get_path(const char *file, char *path, int depth); bool copy_single_file(const std::string& in_file, const std::string& out_file, transform_function_t transform); void fill_rpc_default_files(); }; class HttpController : public CommandController { public: void print_usage(const char *name) const override; private: bool get_opt(int argc, const char **argv) override; public: HttpController(); ~HttpController() { } }; class RedisController : public CommandController { public: void print_usage(const char *name) const override; private: bool get_opt(int argc, const char **argv) override; public: RedisController(); ~RedisController() { } }; class RPCController : public CommandController { public: void print_usage(const char *name) const override; bool copy_files() override; protected: bool check_args() override; private: bool get_opt(int argc, const char **argv) override; public: RPCController(); ~RPCController() { } }; class APIController : public CommandController { public: bool dependencies_and_dir() override; void print_usage(const char *name) const override; void print_success_info() const override; protected: bool check_args() override { return true; } private: bool get_opt(int argc, const char **argv) override; public: APIController(); ~APIController() { } }; class ProxyController : public CommandController { public: void print_usage(const char *name) const override; void print_success_info() const override; bool copy_files() override; protected: bool check_args() override; private: bool get_opt(int argc, const char **argv) override; public: ProxyController(); ~ProxyController() { } }; class FileServiceController : public CommandController { public: void print_usage(const char *name) const override; void print_success_info() const override; private: bool get_opt(int argc, const char **argv) override; public: FileServiceController(); ~FileServiceController() { } }; class ComputeController : public CommandController { public: void print_usage(const char *name) const override; void print_success_info() const override; private: bool get_opt(int argc, const char **argv) override; public: ComputeController(); ~ComputeController() { } }; ////////// common functions ////////// int mkdir_p(const char *name, mode_t mode); ////////// common transform functions ////////// bool common_cmake_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool basic_client_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool basic_server_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool basic_client_config_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool basic_server_config_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool rpc_idl_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool rpc_client_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool rpc_server_transform(const std::string& format, FILE *out, const struct srpc_config *config); bool rpc_cmake_transform(const std::string& format, FILE *out, const struct srpc_config *config); #endif sogou-srpc-ac65014/tools/srpc_ctl.cc000066400000000000000000000051031525217424400174250ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #include #include "srpc_controller.h" static void usage(const char *name) { printf(COLOR_PINK"Description:\n" COLOR_PURPLE" Simple generator for building Workflow and SRPC projects.\n\n" COLOR_PINK"Usage:\n" COLOR_INFO" %s" COLOR_COMMAND " " COLOR_INFO" " COLOR_FLAG " [FLAGS]\n\n" COLOR_PINK"Available Commands:\n" COLOR_COMMAND" http" COLOR_WHITE" - create project with both client and server\n" COLOR_COMMAND" redis" COLOR_WHITE" - create project with both client and server\n" COLOR_COMMAND" rpc" COLOR_WHITE" - create project with both client and server\n" COLOR_COMMAND" api" COLOR_WHITE" - create protobuf or thrift IDL api\n" COLOR_COMMAND" proxy" COLOR_WHITE" - create proxy for some client and server protocol\n" COLOR_COMMAND" file" COLOR_WHITE" - create project with asynchronous file service\n" COLOR_COMMAND" compute" COLOR_WHITE" - create project with asynchronous computing service\n" COLOR_OFF, name); } int main(int argc, const char *argv[]) { if (argc < 2) { usage(argv[0]); return 0; } CommandController *ctl; if (strcasecmp(argv[1], "http") == 0) { ctl = new HttpController; } else if (strcasecmp(argv[1], "redis") == 0) { ctl = new RedisController; } else if (strcasecmp(argv[1], "rpc") == 0) { ctl = new RPCController; } else if (strcasecmp(argv[1], "api") == 0) { ctl = new APIController; } else if (strcasecmp(argv[1], "proxy") == 0) { ctl = new ProxyController; } else if (strcasecmp(argv[1], "file") == 0) { ctl = new FileServiceController; } else if (strcasecmp(argv[1], "compute") == 0) { ctl = new ComputeController; } else { usage(argv[0]); return 0; } if (ctl->parse_args(argc, argv) == true) { if (ctl->dependencies_and_dir() == true) { if (ctl->copy_files() == true) ctl->print_success_info(); } } else ctl->print_usage(argv[0]); delete ctl; return 0; } sogou-srpc-ac65014/tools/srpc_proxy_controller.cc000066400000000000000000000302761525217424400223000ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include #include "srpc_controller.h" static std::string default_server_port(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return "8080"; if (type == PROTOCOL_TYPE_REDIS) return "6379"; if (type == PROTOCOL_TYPE_MYSQL) return "3306"; // Add other protocol here return "1412"; } static std::string default_proxy_port(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return "8888"; if (type == PROTOCOL_TYPE_REDIS) return "6378"; if (type == PROTOCOL_TYPE_MYSQL) return "3305"; // Add other protocol here return "1411"; } static std::string proxy_process_request_codes(uint8_t server_type, uint8_t client_type) { if (server_type == client_type) return std::string(R"( *client_task->get_req() = std::move(*req); )"); else return std::string(R"( { // TODO: fill the client request to server request } )"); } static std::string proxy_callback_response_codes(uint8_t server_type, uint8_t client_type) { if (server_type != client_type) return std::string(R"( { // TODO: fill the server response to client response } )"); if (server_type == PROTOCOL_TYPE_HTTP) return std::string(R"( { const void *body; size_t len; resp->get_parsed_body(&body, &len); resp->append_output_body_nocopy(body, len); *proxy_resp = std::move(*resp); } else { proxy_resp->set_status_code("404"); proxy_resp->append_output_body_nocopy( "404 Not Found.", 27); } )"); return std::string(R"( *proxy_resp = std::move(*resp); )"); } static std::string proxy_redirect_codes(uint8_t type) { if (type == PROTOCOL_TYPE_HTTP) return std::string(R"( config.redirect_max(),)"); return std::string(""); } static bool proxy_basic_transform(const std::string& format, FILE *out, const struct srpc_config *config) { const char *server_type = config->proxy_server_type_string(); const char *client_type = config->proxy_client_type_string(); std::string server_lower = server_type; std::transform(server_lower.begin(), server_lower.end(), server_lower.begin(), ::tolower); std::string server_port = default_server_port(config->proxy_server_type); std::string proxy_port = default_proxy_port(config->proxy_client_type); size_t len = fprintf(out, format.c_str(), client_type, server_type, server_type, client_type, client_type, proxy_callback_response_codes(config->proxy_server_type, config->proxy_client_type).c_str(), // process client_type, client_type, server_lower.c_str(), server_type, server_lower.c_str(), proxy_redirect_codes(config->proxy_server_type).c_str(), proxy_process_request_codes(config->proxy_server_type, config->proxy_client_type).c_str(), // main client_type, server_type, client_type); return len > 0; } static bool proxy_config_transform(const std::string& format, FILE *out, const struct srpc_config *config) { std::string server_port = default_server_port(config->proxy_server_type); std::string proxy_port = default_proxy_port(config->proxy_client_type); size_t len = fprintf(out, format.c_str(), proxy_port.c_str(), server_port.c_str()); return len > 0; } static bool proxy_rpc_proxy_transform(const std::string& format, FILE *out, const struct srpc_config *config) { const char *server_type = config->proxy_server_type_string(); const char *client_type = config->proxy_client_type_string(); size_t len = fprintf(out, format.c_str(), config->project_name, // not support specified idl file config->project_name, config->project_name, config->project_name, server_type, // main client_type, config->project_name, client_type, server_type); return len > 0; } ProxyController::ProxyController() { this->config.type = COMMAND_PROXY; this->config.proxy_client_type = PROTOCOL_TYPE_HTTP; this->config.proxy_server_type = PROTOCOL_TYPE_HTTP; struct file_info info; info = { "proxy/proxy.conf", "proxy.conf", proxy_config_transform }; this->default_files.push_back(info); info = { "basic/server.conf", "server.conf", basic_server_config_transform }; this->default_files.push_back(info); info = { "basic/client.conf", "client.conf", basic_client_config_transform }; this->default_files.push_back(info); info = { "common/GNUmakefile", "GNUmakefile", nullptr }; this->default_files.push_back(info); info = { "config/Json.h", "config/Json.h", nullptr }; this->default_files.push_back(info); info = { "config/Json.cc", "config/Json.cc", nullptr }; this->default_files.push_back(info); } void ProxyController::print_usage(const char *name) const { printf(COLOR_PINK"Usage:\n" COLOR_INFO" %s " COLOR_BLUE "proxy " COLOR_INFO "" COLOR_FLAG " [FLAGS]\n\n" COLOR_PINK"Example:\n" COLOR_PURPLE" %s proxy my_proxy_project\n\n" COLOR_PINK"Available Flags:\n" COLOR_FLAG" -c :" COLOR_WHITE" client type for proxy [ Http | Redis | SRPC | SRPCHttp" " | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http)\n" COLOR_FLAG" -s :" COLOR_WHITE" server type for proxy [ Http | Redis | SRPC | SRPCHttp" " | BRPC | Thrift | ThriftHttp | TRPC | TRPCHttp ] (default: Http)\n" COLOR_FLAG" -o :" COLOR_WHITE" project output path (default: CURRENT_PATH)\n" COLOR_FLAG" -d :" COLOR_WHITE" path of dependencies (default: COMPILE_PATH)\n" COLOR_OFF, name, name); } void ProxyController::print_success_info() const { printf(COLOR_GREEN"Success:\n make project path " COLOR_BLUE" %s " COLOR_GREEN " done.\n\n" COLOR_OFF, this->config.output_path); printf(COLOR_PINK"Commands:\n" COLOR_BLUE " cd %s\n make -j\n\n" COLOR_OFF, this->config.output_path); printf(COLOR_PINK"Execute:\n" COLOR_GREEN" ./server\n ./proxy\n ./client\n\n" COLOR_OFF); } bool ProxyController::copy_files() { struct file_info info; if (check_proxy_type(this->config.proxy_client_type) == PROXY_BASIC_TYPE && check_proxy_type(this->config.proxy_server_type) == PROXY_BASIC_TYPE) { info = { "common/CMakeLists.txt", "CMakeLists.txt", common_cmake_transform }; this->default_files.push_back(info); info = { "common/util.h", "config/util.h", nullptr }; this->default_files.push_back(info); info = { "proxy/proxy_main.cc", "proxy_main.cc", proxy_basic_transform }; this->default_files.push_back(info); info = { "basic/client_main.cc", "client_main.cc", basic_client_transform }; this->default_files.push_back(info); info = { "basic/server_main.cc", "server_main.cc", basic_server_transform }; this->default_files.push_back(info); info = { "config/config_simple.h", "config/config.h", nullptr }; this->default_files.push_back(info); info = { "config/config_simple.cc", "config/config.cc", nullptr }; this->default_files.push_back(info); } else { std::string proxy_main = "proxy/proxy_main_"; if (this->config.idl_type == IDL_TYPE_PROTOBUF) proxy_main += "proto.cc"; else proxy_main += "thrift.cc"; info = { std::move(proxy_main), "proxy_main.cc", proxy_rpc_proxy_transform }; this->default_files.push_back(info); info = { "rpc/CMakeLists.txt", "CMakeLists.txt", rpc_cmake_transform }; this->default_files.push_back(info); info = { "config/config_full.h", "config/config.h", nullptr }; this->default_files.push_back(info); info = { "config/config_full.cc", "config/config.cc", nullptr }; this->default_files.push_back(info); if (this->config.specified_idl_file == NULL) this->fill_rpc_default_files(); else return false; // TODO: NOT supported yet } return CommandController::copy_files(); } static uint8_t proxy_string_to_type(const char *type) { if (strcasecmp(type, "http") == 0) return PROTOCOL_TYPE_HTTP; else if (strcasecmp(type, "redis") == 0) return PROTOCOL_TYPE_REDIS; else if (strcasecmp(type, "mysql") == 0) return PROTOCOL_TYPE_MYSQL; else if (strcasecmp(type, "kafka") == 0) return PROTOCOL_TYPE_KAFKA; else if (strcasecmp(type, "SRPC") == 0) return PROTOCOL_TYPE_SRPC; else if (strcasecmp(type, "SRPCHttp") == 0) return PROTOCOL_TYPE_SRPC_HTTP; else if (strcasecmp(type, "BRPC") == 0) return PROTOCOL_TYPE_BRPC; else if (strcasecmp(type, "TRPC") == 0) return PROTOCOL_TYPE_TRPC; else if (strcasecmp(type, "TRPCHttp") == 0) return PROTOCOL_TYPE_TRPC_HTTP; else if (strcasecmp(type, "Thrift") == 0) return PROTOCOL_TYPE_THRIFT; else if (strcasecmp(type, "ThriftHTTP") == 0) return PROTOCOL_TYPE_THRIFT_HTTP; return PROTOCOL_TYPE_MAX; } static bool proxy_get_opt(int argc, const char **argv, struct srpc_config *config) { int c; while ((c = getopt(argc, (char * const *)argv, "o:c:s:d:")) >= 0) { switch (c) { case 'o': if (sscanf(optarg, "%s", config->output_path) != 1) return false; break; case 'c': config->proxy_client_type = proxy_string_to_type(optarg); break; case 's': config->proxy_server_type = proxy_string_to_type(optarg); break; case 'd': config->specified_depend_path = true; memset(config->depend_path, 0, MAXPATHLEN); if (sscanf(optarg, "%s", config->depend_path) != 1) return false; break; default: printf(COLOR_RED"Error:\n Unknown args : " COLOR_BLUE"%s\n\n" COLOR_OFF, argv[optind - 1]); return false; } } return true; } bool ProxyController::get_opt(int argc, const char **argv) { optind = 2; getcwd(this->config.output_path, MAXPATHLEN); if (proxy_get_opt(argc, argv, &this->config) == false) return false; if (optind == argc) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } this->config.project_name = argv[optind]; optind++; if (proxy_get_opt(argc, argv, &this->config) == false) return false; if (this->config.project_name == NULL) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } this->config.service_name = this->config.project_name; return true; } bool ProxyController::check_args() { int server_type = check_proxy_type(this->config.proxy_server_type); int client_type = check_proxy_type(this->config.proxy_client_type); if (CommandController::check_args() == false) return false; if (client_type < 0 || server_type < 0) { printf(COLOR_RED"Error:\n Invalid type :" COLOR_BLUE" %s" COLOR_RED ", " COLOR_BLUE "%s\n\n" COLOR_OFF, this->config.proxy_server_type_string(), this->config.proxy_client_type_string()); return false; } if ((client_type == PROXY_BASIC_TYPE && server_type > PROXY_BASIC_TYPE) || (server_type == PROXY_BASIC_TYPE && client_type > PROXY_BASIC_TYPE) || // TODO: temperarily only support workflow to workflow, rpc to rpc (client_type == PROXY_PROTOBUF_TYPE && server_type == PROXY_THRIFT_TYPE) || (server_type == PROXY_PROTOBUF_TYPE && client_type == PROXY_THRIFT_TYPE)) // TODO: temperarily NOT support protobuf with thrift { printf(COLOR_RED"Error:\n Temperarily not support " COLOR_BLUE"%s" COLOR_RED " and " COLOR_BLUE "%s" COLOR_RED" together\n\n" COLOR_OFF, this->config.proxy_server_type_string(), this->config.proxy_client_type_string()); return false; } if (client_type == PROXY_PROTOBUF_TYPE) { this->config.idl_type = IDL_TYPE_PROTOBUF; } else if (client_type == PROXY_THRIFT_TYPE) { // this->config.idl_type = IDL_TYPE_THRIFT; printf(COLOR_RED"Error:\n Temperarily not support IDL thrift.\n\n" COLOR_OFF); return false; } return true; } sogou-srpc-ac65014/tools/srpc_rpc_controller.cc000066400000000000000000000264031525217424400217000ustar00rootroot00000000000000/* Copyright (c) 2022 Sogou, Inc. 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. */ #include #include #include #include #include #include #include #include #include "srpc_controller.h" RPCController::RPCController() { this->config.type = COMMAND_RPC; struct file_info info; info = { "rpc/CMakeLists.txt", "CMakeLists.txt", rpc_cmake_transform }; this->default_files.push_back(info); info = { "common/config.json", "client.conf", nullptr }; this->default_files.push_back(info); info = { "common/config.json", "server.conf", nullptr }; this->default_files.push_back(info); info = { "common/GNUmakefile", "GNUmakefile", nullptr }; this->default_files.push_back(info); info = { "config/Json.h", "config/Json.h", nullptr }; this->default_files.push_back(info); info = { "config/Json.cc", "config/Json.cc", nullptr }; this->default_files.push_back(info); info = { "config/config_full.h", "config/config.h", nullptr }; this->default_files.push_back(info); info = { "config/config_full.cc", "config/config.cc", nullptr }; this->default_files.push_back(info); info = { "common/config.json", "full.conf", nullptr }; this->default_files.push_back(info); } void RPCController::print_usage(const char *name) const { printf(COLOR_PINK"Usage:\n" COLOR_INFO" %s " COLOR_COMMAND "rpc " COLOR_INFO" " COLOR_FLAG "[FLAGS]\n\n" COLOR_PINK"Example:\n" COLOR_PURPLE" %s rpc my_rpc_project\n\n" COLOR_PINK"Available Flags:\n" COLOR_FLAG" -r " COLOR_WHITE": rpc type [ SRPC | SRPCHttp | BRPC | Thrift | " "ThriftHttp | TRPC | TRPCHttp ] (default: SRPC)\n" COLOR_FLAG" -o " COLOR_WHITE": project output path (default: CURRENT_PATH)\n" COLOR_FLAG" -s " COLOR_WHITE": service name (default: PROJECT_NAME)\n" COLOR_FLAG" -i " COLOR_WHITE": idl type [ protobuf | thrift ] (default: protobuf)\n" COLOR_FLAG" -x " COLOR_WHITE": data type [ protobuf | thrift | json ] " "(default: idl type. json for http)\n" COLOR_FLAG" -c " COLOR_WHITE": compress type [ gzip | zlib | snappy | lz4 ] " "(default: no compression)\n" COLOR_FLAG" -d " COLOR_WHITE": path of dependencies (default: COMPILE_PATH)\n" COLOR_FLAG" -f " COLOR_WHITE": specify the idl_file to generate codes " "(default: templates/rpc/IDL_FILE)\n" COLOR_FLAG" -p " COLOR_WHITE": specify the path for idl_file to depend " "(default: templates/rpc/)\n" COLOR_OFF, name, name); } bool RPCController::copy_files() { struct srpc_config *config = &this->config; int ret = true; if (config->specified_idl_file == NULL) // fill the default rpc files { this->fill_rpc_default_files(); } else { // copy specified idl file and generate rpc files struct GeneratorParams params; params.out_dir = config->output_path; params.input_dir = config->specified_idl_path; if (params.input_dir[params.input_dir.length() - 1] != '/') params.input_dir += "/"; params.idl_file = config->specified_idl_file; auto pos = params.idl_file.find_last_of('/'); if (pos != std::string::npos) params.idl_file = params.idl_file.substr(pos + 1); std::string out_file = std::string(config->output_path) + std::string("/") + params.idl_file; if (CommandController::copy_single_file(config->specified_idl_file, out_file, nullptr) == false) return false; ControlGenerator gen(config); printf(COLOR_PURPLE"Info: srpc generator begin.\n" COLOR_OFF); ret = gen.generate(params); if (ret == false) { printf(COLOR_RED"Error: srpc generator error.\n\n" COLOR_OFF); return false; } printf(COLOR_PURPLE"Info: srpc generator done.\n\n" COLOR_OFF); } return CommandController::copy_files(); } static bool rpc_get_opt(int argc, const char **argv, struct srpc_config *config) { int c; while ((c = getopt(argc, (char * const *)argv, "o:r:i:x:c:s:d:f:p:")) >= 0) { switch (c) { case 'o': if (sscanf(optarg, "%s", config->output_path) != 1) return false; break; case 'r': config->set_rpc_type(optarg); break; case 'i': config->set_idl_type(optarg); break; case 'x': config->set_data_type(optarg); break; case 'c': config->set_compress_type(optarg); break; case 's': config->service_name = optarg; break; case 'd': config->specified_depend_path = true; memset(config->depend_path, 0, MAXPATHLEN); if (sscanf(optarg, "%s", config->depend_path) != 1) return false; break; case 'f': config->specified_idl_file = optarg; break; case 'p': config->specified_idl_path = optarg; break; default: printf(COLOR_RED "Error:\n Unknown args : " COLOR_BLUE "%s\n\n" COLOR_OFF, argv[optind - 1]); return false; } } return true; } bool RPCController::get_opt(int argc, const char **argv) { optind = 2; if (rpc_get_opt(argc, argv, &this->config) == false) return false; if (optind == argc) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } this->config.project_name = argv[optind]; optind++; if (rpc_get_opt(argc, argv, &this->config) == false) return false; if (this->config.project_name == NULL) { printf(COLOR_RED "Missing: PROJECT_NAME\n\n" COLOR_OFF); return false; } return true; } bool RPCController::check_args() { if (CommandController::check_args() == false) return false; struct srpc_config *config = &this->config; if (config->rpc_type == PROTOCOL_TYPE_MAX || config->idl_type == IDL_TYPE_MAX || config->data_type == DATA_TYPE_MAX || config->compress_type == COMPRESS_TYPE_MAX) { printf(COLOR_RED"Error:\n Invalid rpc args : -r | -i | -c | -d .\n\n" COLOR_OFF); return false; } switch (config->rpc_type) { case PROTOCOL_TYPE_THRIFT: case PROTOCOL_TYPE_THRIFT_HTTP: if (config->idl_type == IDL_TYPE_PROTOBUF || config->data_type == DATA_TYPE_PROTOBUF) { printf(COLOR_RED"Error:\n " COLOR_BLUE"\" %s \" " COLOR_RED"does NOT support protobuf as idl or data type.\n\n" COLOR_OFF, config->rpc_type_string()); return false; } if (config->idl_type == IDL_TYPE_DEFAULT) config->idl_type = IDL_TYPE_THRIFT; // as default; break; case PROTOCOL_TYPE_BRPC: case PROTOCOL_TYPE_TRPC: case PROTOCOL_TYPE_TRPC_HTTP: if (config->idl_type == IDL_TYPE_THRIFT || config->data_type == DATA_TYPE_THRIFT) { printf(COLOR_RED "Error:\n " COLOR_BLUE "\" %s \" " COLOR_RED "does NOT support thrift as idl or data type.\n" COLOR_OFF, config->rpc_type_string()); return false; } default: if (config->idl_type == IDL_TYPE_DEFAULT) config->idl_type = IDL_TYPE_PROTOBUF; // as default; break; } if (config->prepare_specified_idl_file() == false) return false; if (config->service_name == NULL) config->service_name = config->project_name; return true; } APIController::APIController() { this->config.type = COMMAND_API; this->config.idl_type = IDL_TYPE_PROTOBUF; } void APIController::print_usage(const char *name) const { printf(COLOR_PINK"Usage:\n" COLOR_INFO" %s " COLOR_COMMAND "api " COLOR_INFO" " COLOR_FLAG "[FLAGS]\n\n" COLOR_PINK"Example:\n" COLOR_PURPLE" %s api my_api\n\n" COLOR_PINK"Available Flags:\n" COLOR_FLAG" -o " COLOR_WHITE": file output path (default: CURRENT_PATH)\n" COLOR_FLAG" -i " COLOR_WHITE": idl type [ protobuf | thrift ] (default: protobuf)\n" COLOR_OFF, name, name); } static bool api_get_opt(int argc, const char **argv, struct srpc_config *config) { char c; while ((c = getopt(argc, (char * const *)argv, "o:i:")) != -1) { switch (c) { case 'o': memset(config->output_path, 0, MAXPATHLEN); if (sscanf(optarg, "%s", config->output_path) != 1) return false; break; case 'i': config->set_idl_type(optarg); break; default: printf(COLOR_RED "Error:\n Unknown args : " COLOR_BLUE "%s\n\n" COLOR_OFF, argv[optind - 1]); return false; } } return true; } bool APIController::get_opt(int argc, const char **argv) { optind = 2; getcwd(this->config.output_path, MAXPATHLEN); if (api_get_opt(argc, argv, &this->config) == false) return false; if (optind == argc) { printf(COLOR_RED "Missing: FILE_NAME\n\n" COLOR_OFF); return false; } this->config.project_name = argv[optind]; optind++; if (api_get_opt(argc, argv, &this->config) == false) return false; if (this->config.project_name == NULL) { printf(COLOR_RED "Missing: FILE_NAME\n\n" COLOR_OFF); return false; } this->config.service_name = this->config.project_name; return true; } bool APIController::dependencies_and_dir() { std::string idl_file_name; std::string out_file_name = this->config.project_name; if (this->config.idl_type == IDL_TYPE_PROTOBUF) { out_file_name += ".proto"; idl_file_name = "rpc/rpc.proto"; } else if (this->config.idl_type == IDL_TYPE_THRIFT) { out_file_name += ".thrift"; idl_file_name = "rpc/rpc.thrift"; } std::string abs_file_name = this->config.output_path; if (abs_file_name.at(abs_file_name.length() - 1) != '/') abs_file_name += "/"; abs_file_name += out_file_name; DIR *dir; dir = opendir(this->config.output_path); if (dir == NULL) { if (mkdir_p(this->config.output_path, 0755) != 0) { perror("Error:\n failed to make output_path "); return false; } } else { closedir(dir); struct stat st; if (stat(abs_file_name.c_str(), &st) >= 0) { printf(COLOR_RED"Error:\n file" COLOR_BLUE" %s " COLOR_RED "EXISTED in path" COLOR_BLUE" %s " COLOR_RED ".\n" COLOR_OFF, out_file_name.c_str(), this->config.output_path); return false; } } dir = opendir(this->config.template_path); if (dir == NULL) { printf(COLOR_RED"Error:\n template path " COLOR_BLUE" %s " COLOR_RED "does NOT exist.\n" COLOR_OFF, this->config.template_path); return false; } struct file_info info; info = { idl_file_name , out_file_name, rpc_idl_transform }; this->default_files.push_back(info); closedir(dir); return true; } void APIController::print_success_info() const { std::string file_name = this->config.project_name; if (this->config.idl_type == IDL_TYPE_PROTOBUF) file_name += ".proto"; else if (this->config.idl_type == IDL_TYPE_THRIFT) file_name += ".thrift"; printf(COLOR_GREEN"Success:\n Create api file" COLOR_BLUE" %s " COLOR_GREEN "at path" COLOR_BLUE" %s " COLOR_GREEN "done.\n\n" COLOR_OFF, file_name.c_str(), this->config.output_path); printf(COLOR_PINK"Suggestions:\n" COLOR_WHITE" Modify the api file as you needed.\n" " And make rpc project base on this file with the following command:\n\n" COLOR_GREEN" ./srpc rpc my_rpc_project -f %s -p %s\n\n" COLOR_OFF, file_name.c_str(), this->config.output_path); } sogou-srpc-ac65014/tools/templates/000077500000000000000000000000001525217424400173045ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/basic/000077500000000000000000000000001525217424400203655ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/basic/client.conf000066400000000000000000000001471525217424400225140ustar00rootroot00000000000000{ "client": { "remote_host": "127.0.0.1", "remote_port": %u,%s "retry_max": 2%s } } sogou-srpc-ac65014/tools/templates/basic/client_main.cc000066400000000000000000000021461525217424400231610ustar00rootroot00000000000000#include #include #include "workflow/%sMessage.h" #include "workflow/WFTaskFactory.h" #include "workflow/WFFacilities.h" #include "config/config.h" static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./client.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } void callback(WF%sTask *task) { int state = task->get_state(); int error = task->get_error(); fprintf(stderr, "%s client state = %%d error = %%d\n", state, error); %s } int main() { init(); std::string url = std::string("%s://") + %sconfig.client_host() + std::string(":") + std::to_string(config.client_port()); WF%sTask *task = WFTaskFactory::create_%s_task(url,%s config.retry_max(), callback); %s task->start(); wait_group.wait(); return 0; } sogou-srpc-ac65014/tools/templates/basic/compute_server_main.cc000066400000000000000000000041101525217424400247360ustar00rootroot00000000000000#include #include #include #include "workflow/WFHttpServer.h" #include "workflow/WFFacilities.h" #include "config/config.h" static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./server.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } // Example for Fibonacci. void Fibonacci(int n, protocol::HttpResponse *resp) { unsigned long long x = 0, y = 1; char buf[256]; int i; if (n <= 0 || n > 94) { resp->append_output_body_nocopy("Invalid Number.", strlen("Invalid Number.")); return; } resp->append_output_body_nocopy("", strlen("")); for (i = 2; i < n; i++) { sprintf(buf, "

%llu + %llu = %llu.

", x, y, x + y); resp->append_output_body(buf); y = x + y; x = y - x; } if (n == 1) y = 0; sprintf(buf, "

The No. %d Fibonacci number is: %llu.

", n, y); resp->append_output_body(buf); resp->append_output_body_nocopy("", strlen("")); } void process(WFHttpTask *task) { const char *uri = task->get_req()->get_request_uri(); if (*uri == '/') uri++; int n = atoi(uri); protocol::HttpResponse *resp = task->get_resp(); fprintf(stderr, "server get request. n = %d\n", n); // All calculations can be encapsulated by 'go_task' WFGoTask *go_task = WFTaskFactory::create_go_task("go", Fibonacci, n, resp); // Tasks will be dispatch asynchonously after 'push_back()' series_of(task)->push_back(go_task); } int main() { init(); WFHttpServer server(process); if (server.start(config.server_port()) == 0) { fprintf(stderr, "Computing server started, port %u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tools/templates/basic/server.conf000066400000000000000000000000501525217424400225350ustar00rootroot00000000000000{ "server": { "port": %u } } sogou-srpc-ac65014/tools/templates/basic/server_main.cc000066400000000000000000000015741525217424400232150ustar00rootroot00000000000000#include #include #include "workflow/WF%sServer.h" #include "workflow/WFFacilities.h" #include "config/util.h" #include "config/config.h" static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./server.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } void process(WF%sTask *task) { // delete the example codes and fill your logic %s } int main() { init(); WF%sServer server(process); if (server.start(config.server_port()) == 0) { fprintf(stderr, "%s server started, port %%u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tools/templates/common/000077500000000000000000000000001525217424400205745ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/common/CMakeLists.txt000066400000000000000000000021101525217424400233260ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(%s LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_BUILD_TYPE RelWithDebInfo) # Find all the dependencies find_package(OpenSSL REQUIRED) set(Workflow_DIR "%s") find_package(Workflow REQUIRED CONFIG HINTS ${Workflow_DIR}) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) # Prefer to static link first set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES}) find_library(Workflow_LIB workflow HINTS ${Workflow_DIR}/_lib) # Set all the libraries here set(LIB ${Workflow_LIB} pthread OpenSSL::SSL OpenSSL::Crypto) # Add all the common codes here set(COMMON_CODE config/config.cc config/Json.cc%s) # Add header directories and library directories here include_directories(${OPENSSL_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR}) link_directories(${OPENSSL_LINK_DIR} ${WORKFLOW_LIB_DIR}) # Build executable outputs set(PROJECT_OUTPUT server%s) foreach(output ${PROJECT_OUTPUT}) add_executable(${output} ${output}_main.cc ${COMMON_CODE}) target_link_libraries(${output} ${LIB}) endforeach() sogou-srpc-ac65014/tools/templates/common/GNUmakefile000066400000000000000000000005221525217424400226450ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) BUILD_DIR := build.cmake .PHONY: all clean all: base make -C $(BUILD_DIR) -f Makefile base: mkdir -p $(BUILD_DIR) cd $(BUILD_DIR) && cmake $(ROOT_DIR) clean: ifeq ($(BUILD_DIR), $(wildcard $(BUILD_DIR))) make -C $(BUILD_DIR) clean rm -rf $(BUILD_DIR) endif sogou-srpc-ac65014/tools/templates/common/config.json000066400000000000000000000040601525217424400227340ustar00rootroot00000000000000{ "server": { "port": 8080 }, "client": { "transport_type": "TT_TCP", "remote_host": "127.0.0.1", "remote_port": 8080, "is_ssl" : false, "retry_max": 1, "user_name": "root", "password": "", "callee" : "rpc_client" }, "global": { "poller_threads": 4, "handler_threads": 20, "dns_threads": 4, "compute_threads": -1, "dns_ttl_default": 43200, "dns_ttl_min": 180, "resolv_conf_path": "/etc/resolv.conf", "hosts_path": "/etc/hosts", "endpoint_params": { "max_connections": 200, "connect_timeout": 10000, "response_timeout": 10000, "ssl_connect_timeout": 10000, "use_tls_sni": false }, "dns_server_params": { "max_connections": 200 } }, "metrics":[ { "filter": "prometheus", "port": 8000 }, { "filter": "opentelemetry", "filter_name": "otel_reporter1", "address": "http://opentelemetry.com:4389", "redirect_max": 0, "retry_max": 1, "report_threshold": 100, "report_interval_ms": 1000, "attributes": [ { "key": "tenant.id", "value": "abcd" } ] } ], "trace":[ { "filter": "default", "span_per_second": 1000 }, { "filter": "opentelemetry", "address": "http://opentelemetry.com:4389", "redirect_max": 0, "retry_max": 1, "span_per_second": 1000, "report_threshold": 100, "report_interval_ms": 1000, "attributes": [ { "key": "tenant.id", "value": "abcd" } ] } ], "upstream":[ { "name": "test_weighted_random_host", "type": "weighted_random", "try_another": false, "server":[ { "host": "127.0.0.1:8080", "params": {"weight": 2} }, { "host": "127.0.0.1:8081" } ] }, { "name": "test_consistent_hash_host", "type": "consistent_hash", "server":[ { "host": "127.0.0.1:8083" }, { "host": "127.0.0.1:8084" } ] } ] } sogou-srpc-ac65014/tools/templates/common/util.h000066400000000000000000000017451525217424400217310ustar00rootroot00000000000000#include #include #include #include #include #include "workflow/WFTaskFactory.h" template void print_peer_address(TASK *server_task) { char addrstr[128]; struct sockaddr_storage addr; socklen_t l = sizeof addr; unsigned short port = 0; long long seq = server_task->get_task_seq(); server_task->get_peer_addr((struct sockaddr *)&addr, &l); if (addr.ss_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)&addr; inet_ntop(AF_INET, &sin->sin_addr, addrstr, 128); port = ntohs(sin->sin_port); } else if (addr.ss_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr; inet_ntop(AF_INET6, &sin6->sin6_addr, addrstr, 128); port = ntohs(sin6->sin6_port); } else strcpy(addrstr, "Unknown"); fprintf(stderr, "peer address: %s:%d, seq: %lld.\n", addrstr, port, seq); } sogou-srpc-ac65014/tools/templates/config/000077500000000000000000000000001525217424400205515ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/config/Json.cc000066400000000000000000000574551525217424400220110ustar00rootroot00000000000000#include "Json.h" namespace wfrest { // ------------------------ Constructor ------------------------- Json::Json() : node_(json_value_create(JSON_VALUE_NULL)), parent_(nullptr), allocated_(true) { } Json::Json(const std::string& str) : node_(json_value_create(JSON_VALUE_STRING, str.c_str())), parent_(nullptr), allocated_(true) { } Json::Json(const char* str) : node_(json_value_create(JSON_VALUE_STRING, str)), parent_(nullptr), allocated_(true) { } Json::Json(std::nullptr_t null) : node_(json_value_create(JSON_VALUE_NULL)), parent_(nullptr), allocated_(true) { } Json::Json(bool val) : node_(val ? json_value_create(JSON_VALUE_TRUE) : json_value_create(JSON_VALUE_FALSE)), parent_(nullptr), allocated_(true) { } Json::Json(const std::vector& val) : node_(json_value_create(JSON_VALUE_ARRAY)), parent_(nullptr), allocated_(true) { json_array_t* arr = json_value_array(node_); for (const auto& str : val) { json_array_append(arr, JSON_VALUE_STRING, str.c_str()); } } // for parse Json::Json(const std::string& str, bool parse_flag) : parent_(nullptr) { node_ = json_value_parse(str.c_str()); allocated_ = node_ == nullptr ? false : true; } Json::~Json() { destroy_node(&node_); } // watcher constructor Json::Json(const json_value_t* node, const json_value_t* parent, std::string&& key) : node_(const_cast(node)), parent_(parent), allocated_(false), parent_key_(std::move(key)) { } Json::Json(const json_value_t* node, const json_value_t* parent, const std::string& key) : node_(const_cast(node)), parent_(parent), allocated_(false), parent_key_(key) { } Json::Json(JsonType type) : allocated_(true) { if (type == JsonType::Object) { node_ = json_value_create(JSON_VALUE_OBJECT); } else { node_ = json_value_create(JSON_VALUE_ARRAY); } } Json::Json(const Json& other) { node_ = json_value_copy(other.node_); allocated_ = true; } Json& Json::operator=(const Json& other) { if (this == &other) { return *this; } destroy_node(&node_); node_ = json_value_copy(other.node_); parent_ = nullptr; allocated_ = true; parent_key_.clear(); return *this; } Json::Json(Json&& other) : node_(other.node_), parent_(other.parent_), allocated_(other.allocated_), parent_key_(std::move(other.parent_key_)) { other.node_ = nullptr; other.parent_ = nullptr; other.allocated_ = false; } Json& Json::operator=(Json&& other) { if (this == &other) { return *this; } destroy_node(&node_); node_ = other.node_; other.node_ = nullptr; parent_ = other.parent_; other.parent_ = nullptr; allocated_ = other.allocated_; other.allocated_ = false; parent_key_ = std::move(other.parent_key_); return *this; } Json Json::parse(const std::string& str) { return Json(str, true); } Json Json::parse(const std::ifstream& stream) { std::stringstream buffer; buffer << stream.rdbuf(); return Json(buffer.str(), true); } Json Json::parse(FILE* fp) { if (fp == nullptr) { return Json(); } fseek(fp, 0, SEEK_END); long length = ftell(fp); fseek(fp, 0, SEEK_SET); char* buffer = (char*)malloc(length + 1); buffer[length] = '\0'; long ret = fread(buffer, 1, length, fp); Json js; if (ret != length) { js = Json(); } else { js = Json(buffer, true); } free(buffer); return js; } std::string Json::dump() const { return dump(0); } std::string Json::dump(int spaces) const { std::string str; str.reserve(64); value_convert(node_, spaces, 0, &str); return str; } Json Json::operator[](const char* key) { if (is_null() && is_root()) { // todo : need is_root here? to_object(); } else if (is_object()) { // if exists json_object_t* obj = json_value_object(node_); const json_value_t* res = json_object_find(key, obj); if (res != nullptr) { return Json(res, node_, std::string(key)); } } if (is_placeholder()) { destroy_node(&node_); json_object_t* parent_obj = json_value_object(parent_); node_ = const_cast(json_object_append( parent_obj, parent_key_.c_str(), JSON_VALUE_OBJECT)); } if (!is_object()) { return Json(); } // (null, parent(node_), key) Json js = Json(); js.set_parent(node_, std::string(key)); return js; } Json Json::operator[](const char* key) const { if (!is_object()) { return Json(); } const json_value_t* val = node_; json_object_t* obj = json_value_object(val); const json_value_t* res = json_object_find(key, obj); if (res != nullptr) { return Json(res, node_, ""); } return Json(); } Json Json::operator[](const std::string& key) { return this->operator[](key.c_str()); } Json Json::operator[](const std::string& key) const { return this->operator[](key.c_str()); } bool Json::has(const std::string& key) const { if (!is_object()) return false; json_object_t* obj = json_value_object(node_); const json_value_t* find = json_object_find(key.c_str(), obj); return find != nullptr; } void Json::erase(const std::string& key) { if (!is_object()) return; json_object_t* obj = json_value_object(node_); const json_value_t* find = json_object_find(key.c_str(), obj); if (find == nullptr) return; json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } Json Json::operator[](int index) { if (!is_array() || index < 0 || index > (int)this->size()) { return Json(); } const json_value_t* val; json_array_t* arr = json_value_array(node_); json_array_for_each(val, arr) { if (index == 0) { return Json(val, node_, ""); } index--; } return Json(); } void Json::erase(int index) { if (!is_array()) return; int cnt = 0; json_array_t* arr = json_value_array(node_); const json_value_t* arr_cursor = nullptr; json_array_for_each(arr_cursor, arr) { if (cnt++ == index) break; } json_value_t* remove_val = json_array_remove(arr_cursor, arr); json_value_destroy(remove_val); } Json Json::operator[](int index) const { if (!is_array() || index < 0 || index > (int)this->size()) { return Json(); } const json_value_t* val; json_array_t* arr = json_value_array(node_); json_array_for_each(val, arr) { if (index == 0) { return Json(val, node_, ""); } index--; } return Json(); } bool Json::can_obj_push_back() { if (is_placeholder() || (parent_ != nullptr && json_value_type(parent_) == JSON_VALUE_OBJECT)) { return true; } if (is_root() && is_null()) { to_object(); } return is_object(); } void Json::push_back(const std::string& key, bool val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); int type = val ? JSON_VALUE_TRUE : JSON_VALUE_FALSE; json_object_append(obj, key.c_str(), type); } void Json::push_back(const std::string& key, std::nullptr_t val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); json_object_append(obj, key.c_str(), JSON_VALUE_NULL); } void Json::push_back(const std::string& key, const std::string& val) { push_back(key, val.c_str()); } void Json::push_back(const std::string& key, const char* val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); json_object_append(obj, key.c_str(), JSON_VALUE_STRING, val); } void Json::push_back(const std::string& key, const std::vector& val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); const json_value_t* v = json_object_append(obj, key.c_str(), JSON_VALUE_ARRAY); json_array_t* arr = json_value_array(v); for (const auto& str : val) { json_array_append(arr, JSON_VALUE_STRING, str.c_str()); } } void Json::push_back(const std::string& key, const Json& val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); Json copy_json = val; json_object_append(obj, key.c_str(), 0, copy_json.node_); copy_json.reset(); } void Json::placeholder_push_back(const std::string& key, bool val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); if (val) { node_ = const_cast( json_object_append(obj, key.c_str(), JSON_VALUE_TRUE)); } else { node_ = const_cast( json_object_append(obj, key.c_str(), JSON_VALUE_FALSE)); } } void Json::placeholder_push_back(const std::string& key, std::nullptr_t val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); node_ = const_cast( json_object_append(obj, key.c_str(), JSON_VALUE_NULL)); } void Json::placeholder_push_back(const std::string& key, const std::string& val) { placeholder_push_back(key, val.c_str()); } void Json::placeholder_push_back(const std::string& key, const char* val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); node_ = const_cast( json_object_append(obj, key.c_str(), JSON_VALUE_STRING, val)); } void Json::placeholder_push_back(const std::string& key, const std::vector& val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); node_ = const_cast( json_object_append(obj, key.c_str(), JSON_VALUE_ARRAY)); json_array_t* arr = json_value_array(node_); for (const auto& str : val) json_array_append(arr, JSON_VALUE_STRING, str.c_str()); } void Json::placeholder_push_back(const std::string& key, const Json& val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); Json copy_json = val; node_ = const_cast( json_object_append(obj, key.c_str(), 0, copy_json.node_)); copy_json.reset(); } void Json::normal_push_back(const std::string& key, bool val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); int type = val ? JSON_VALUE_TRUE : JSON_VALUE_FALSE; if (find == nullptr) { json_object_append(obj, key.c_str(), type); return; } json_object_insert_before(find, obj, key.c_str(), type); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } void Json::normal_push_back(const std::string& key, std::nullptr_t val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); if (find == nullptr) { json_object_append(obj, key.c_str(), JSON_VALUE_NULL); return; } json_object_insert_before(find, obj, key.c_str(), JSON_VALUE_NULL); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } void Json::normal_push_back(const std::string& key, const std::string& val) { normal_push_back(key, val.c_str()); } void Json::normal_push_back(const std::string& key, const char* val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); if (find == nullptr) { json_object_append(obj, key.c_str(), JSON_VALUE_STRING, val); return; } json_object_insert_before(find, obj, key.c_str(), JSON_VALUE_STRING, val); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } void Json::normal_push_back(const std::string& key, const std::vector& val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); const json_value_t* v; if (find == nullptr) { v = json_object_append(obj, key.c_str(), JSON_VALUE_ARRAY); } else { v = json_object_insert_before(find, obj, key.c_str(), JSON_VALUE_ARRAY); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } json_array_t* arr = json_value_array(v); for (const auto& str : val) json_array_append(arr, JSON_VALUE_STRING, str.c_str()); } void Json::normal_push_back(const std::string& key, const Json& val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); Json copy_json = val; if (find == nullptr) { json_object_append(obj, key.c_str(), 0, copy_json.node_); copy_json.node_ = nullptr; return; } json_object_insert_before(find, obj, key.c_str(), 0, copy_json.node_); copy_json.reset(); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } bool Json::can_arr_push_back() { if (is_root() && is_null()) { to_array(); } return is_array(); } Json Json::copy() const { return *this; } void Json::push_back(bool val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); int type = val ? JSON_VALUE_TRUE : JSON_VALUE_FALSE; json_array_append(arr, type); } void Json::push_back(std::nullptr_t val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); json_array_append(arr, JSON_VALUE_NULL); } void Json::push_back(const std::string& val) { push_back(val.c_str()); } void Json::push_back(const std::vector& val) { for (const auto& str : val) { push_back(str.c_str()); } } void Json::push_back(const char* val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); json_array_append(arr, JSON_VALUE_STRING, val); } void Json::push_back(const Json& val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); Json copy_json = val; json_array_append(arr, 0, copy_json.node_); copy_json.reset(); } void Json::update_arr(bool val) { json_array_t* arr = json_value_array(parent_); int type = val ? JSON_VALUE_TRUE : JSON_VALUE_FALSE; json_array_insert_before(node_, arr, type); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } void Json::update_arr(std::nullptr_t val) { json_array_t* arr = json_value_array(parent_); json_array_insert_before(node_, arr, JSON_VALUE_NULL); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } void Json::update_arr(const std::string& val) { update_arr(val.c_str()); } void Json::update_arr(const char* val) { json_array_t* arr = json_value_array(parent_); json_array_insert_before(node_, arr, JSON_VALUE_STRING, val); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } void Json::update_arr(const std::vector& val) { json_array_t* arr = json_value_array(parent_); const json_value_t* v = json_array_insert_before(node_, arr, JSON_VALUE_ARRAY); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); arr = json_value_array(v); for (const auto& str : val) { json_array_append(arr, JSON_VALUE_STRING, str.c_str()); } } void Json::update_arr(const Json& val) { json_array_t* arr = json_value_array(parent_); Json copy_json = val; json_array_insert_before(node_, arr, 0, copy_json.node_); copy_json.reset(); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } std::string Json::type_str() const { switch (type()) { case JSON_VALUE_STRING: return "string"; case JSON_VALUE_NUMBER: return "number"; case JSON_VALUE_OBJECT: return "object"; case JSON_VALUE_ARRAY: return "array"; case JSON_VALUE_TRUE: return "true"; case JSON_VALUE_FALSE: return "false"; case JSON_VALUE_NULL: return "null"; } return "unknown"; } size_t Json::size() const { if (type() == JSON_VALUE_ARRAY) { json_array_t* array = json_value_array(node_); return json_array_size(array); } else if (type() == JSON_VALUE_OBJECT) { json_object_t* obj = json_value_object(node_); return json_object_size(obj); } return 1; } bool Json::empty() const { switch (type()) { case JSON_VALUE_NULL: { // null values are empty return true; } case JSON_VALUE_ARRAY: case JSON_VALUE_OBJECT: { return size() == 0; } default: // all other types are nonempty return false; } } void Json::clear() { int type = json_value_type(node_); destroy_node(&node_); parent_ = nullptr; allocated_ = true; parent_key_.clear(); if (type == JSON_VALUE_STRING) { node_ = json_value_create(JSON_VALUE_STRING, ""); } else if (type == JSON_VALUE_NUMBER) { node_ = json_value_create(JSON_VALUE_NUMBER, 0.0); } else { node_ = json_value_create(type); } } bool Json::to_object() { if (!allocated_ || !is_null()) { // watcher and non-null type can't change type return false; } destroy_node(&node_); node_ = json_value_create(JSON_VALUE_OBJECT); allocated_ = true; return true; } bool Json::to_array() { if (!allocated_ || !is_null()) { // watcher and non-null type can't change type return false; } destroy_node(&node_); node_ = json_value_create(JSON_VALUE_ARRAY); allocated_ = true; return true; } void Json::value_convert(const json_value_t* val, int spaces, int depth, std::string* out_str) { if (val == nullptr || out_str == nullptr) return; switch (json_value_type(val)) { case JSON_VALUE_STRING: string_convert(json_value_string(val), out_str); break; case JSON_VALUE_NUMBER: number_convert(json_value_number(val), out_str); break; case JSON_VALUE_OBJECT: object_convert(json_value_object(val), spaces, depth, out_str); break; case JSON_VALUE_ARRAY: array_convert(json_value_array(val), spaces, depth, out_str); break; case JSON_VALUE_TRUE: out_str->append("true"); break; case JSON_VALUE_FALSE: out_str->append("false"); break; case JSON_VALUE_NULL: out_str->append("null"); break; } } void Json::string_convert(const char* str, std::string* out_str) { out_str->append("\""); while (*str) { switch (*str) { case '\r': out_str->append("\\r"); break; case '\n': out_str->append("\\n"); break; case '\f': out_str->append("\\f"); break; case '\b': out_str->append("\\b"); break; case '\"': out_str->append("\\\""); break; case '\t': out_str->append("\\t"); break; case '\\': out_str->append("\\\\"); break; default: if ((unsigned char)*str < 0x20) { char buf[8]; snprintf(buf, 8, "\\u00%02x", *str); out_str->append(buf); } else out_str->push_back(*str); break; } str++; } out_str->append("\""); } void Json::number_convert(double number, std::string* out_str) { char buf[32]; snprintf(buf, 32, "%.15lg", number); out_str->append(buf); } void Json::array_convert_not_format(const json_array_t* arr, std::string* out_str) { const json_value_t* val; int n = 0; out_str->append("["); json_array_for_each(val, arr) { if (n != 0) { out_str->append(","); } n++; value_convert(val, 0, 0, out_str); } out_str->append("]"); } void Json::array_convert(const json_array_t* arr, int spaces, int depth, std::string* out_str) { if (spaces == 0) { return array_convert_not_format(arr, out_str); } const json_value_t* val; int n = 0; int i; std::string padding(spaces, ' '); out_str->append("[\n"); json_array_for_each(val, arr) { if (n != 0) { out_str->append(",\n"); } n++; for (i = 0; i < depth + 1; i++) { out_str->append(padding); } value_convert(val, spaces, depth + 1, out_str); } out_str->append("\n"); for (i = 0; i < depth; i++) { out_str->append(padding); } out_str->append("]"); } void Json::object_convert_not_format(const json_object_t* obj, std::string* out_str) { const char* name; const json_value_t* val; int n = 0; out_str->append("{"); json_object_for_each(name, val, obj) { if (n != 0) { out_str->append(","); } n++; string_convert(name, out_str); out_str->append(":"); value_convert(val, 0, 0, out_str); } out_str->append("}"); } void Json::object_convert(const json_object_t* obj, int spaces, int depth, std::string* out_str) { if (spaces == 0) { return object_convert_not_format(obj, out_str); } const char* name; const json_value_t* val; int n = 0; int i; std::string padding(spaces, ' '); out_str->append("{\n"); json_object_for_each(name, val, obj) { if (n != 0) { out_str->append(",\n"); } n++; for (i = 0; i < depth + 1; i++) { out_str->append(padding); } string_convert(name, out_str); out_str->append(": "); value_convert(val, spaces, depth + 1, out_str); } out_str->append("\n"); for (i = 0; i < depth; i++) { out_str->append(padding); } out_str->append("}"); } } // namespace wfrest sogou-srpc-ac65014/tools/templates/config/Json.h000066400000000000000000000571261525217424400216460ustar00rootroot00000000000000#ifndef WFREST_JSON_H_ #define WFREST_JSON_H_ #include "workflow/json_parser.h" #include #include #include #include #include #include #include #include #include #include #include namespace wfrest { namespace detail { template struct is_string { static constexpr bool value = false; }; template struct is_string> { static constexpr bool value = true; }; template struct is_char : std::integral_constant::value || std::is_same::value || std::is_same::value || std::is_same::value> { }; template struct is_number : std::integral_constant::value && !std::is_same::value && !detail::is_char::value> { }; } // namespace detail class Object_S; class Array_S; class Json { public: using Object = Object_S; using Array = Array_S; public: static Json parse(const std::string& str); static Json parse(const std::ifstream& stream); static Json parse(FILE* fp); std::string dump() const; std::string dump(int spaces) const; // for correctness in clang Json operator[](const char* key); Json operator[](const char* key) const; Json operator[](const std::string& key); Json operator[](const std::string& key) const; Json operator[](int index); Json operator[](int index) const; template void operator=(const T& val) { if (parent_ == nullptr) return; if (json_value_type(parent_) == JSON_VALUE_ARRAY) { update_arr(val); } else if (json_value_type(parent_) == JSON_VALUE_OBJECT) { push_back_obj(parent_key_, val); } } bool has(const std::string& key) const; template typename std::enable_if::value, T>::type get() const { return json_value_type(node_) == JSON_VALUE_TRUE ? true : false; } template typename std::enable_if::value, T>::type get() const { return static_cast(json_value_number(node_)); } template typename std::enable_if::value, T>::type get() const { return std::string(json_value_string(node_)); } template typename std::enable_if::value, T>::type get() const; template typename std::enable_if::value, T>::type get() const; template typename std::enable_if::value, T>::type get() const { return nullptr; } template operator T() { return get(); } public: int type() const { return json_value_type(node_); } std::string type_str() const; bool is_null() const { return type() == JSON_VALUE_NULL; } bool is_number() const { return type() == JSON_VALUE_NUMBER; } bool is_boolean() const { int type = this->type(); return type == JSON_VALUE_TRUE || type == JSON_VALUE_FALSE; } bool is_object() const { return type() == JSON_VALUE_OBJECT; } bool is_array() const { return type() == JSON_VALUE_ARRAY; } bool is_string() const { return type() == JSON_VALUE_STRING; } bool is_valid() const { return node_ != nullptr; } size_t size() const; bool empty() const; void clear(); Json copy() const; std::string key() const { return parent_key_; } const Json& value() const { return *this; } public: // for object template ::value, bool>::type = true> void push_back(const std::string& key, const T& val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); json_object_append(obj, key.c_str(), JSON_VALUE_NUMBER, static_cast(val)); } template ::value || std::is_same::value, bool>::type = true> void push_back(const std::string& key, const T& val) { if (is_placeholder()) { *this = Json::Object{{key, val}}; return; } if (!can_obj_push_back()) { return; } json_object_t* obj = json_value_object(node_); Json copy_json = val.copy(); json_object_append(obj, key.c_str(), 0, copy_json.node_); copy_json.reset(); } void push_back(const std::string& key, bool val); void push_back(const std::string& key, std::nullptr_t val); void push_back(const std::string& key, const std::string& val); void push_back(const std::string& key, const char* val); void push_back(const std::string& key, const std::vector& val); void push_back(const std::string& key, const std::initializer_list& val) { push_back(key, std::vector(val)); } void push_back(const std::string& key, const Json& val); private: // for object template void push_back_obj(const std::string& key, const T& val) { if (!can_obj_push_back()) { return; } if (is_placeholder()) { placeholder_push_back(key, val); } else { normal_push_back(key, val); } } template ::value, bool>::type = true> void placeholder_push_back(const std::string& key, const T& val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); node_ = const_cast(json_object_append( obj, key.c_str(), JSON_VALUE_NUMBER, static_cast(val))); } template ::value || std::is_same::value, bool>::type = true> void placeholder_push_back(const std::string& key, const T& val) { json_object_t* obj = json_value_object(parent_); destroy_node(&node_); Json copy_json = val.copy(); node_ = const_cast( json_object_append(obj, key.c_str(), 0, copy_json.node_)); copy_json.reset(); } void placeholder_push_back(const std::string& key, bool val); void placeholder_push_back(const std::string& key, std::nullptr_t val); void placeholder_push_back(const std::string& key, const std::string& val); void placeholder_push_back(const std::string& key, const char* val); void placeholder_push_back(const std::string& key, const std::vector& val); void placeholder_push_back(const std::string& key, const Json& val); template ::value, bool>::type = true> void normal_push_back(const std::string& key, const T& val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); if (find == nullptr) { json_object_append(obj, key.c_str(), JSON_VALUE_NUMBER, static_cast(val)); return; } json_object_insert_before(find, obj, key.c_str(), JSON_VALUE_NUMBER, static_cast(val)); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } template ::value || std::is_same::value, bool>::type = true> void normal_push_back(const std::string& key, const T& val) { json_object_t* obj = json_value_object(parent_); const json_value_t* find = json_object_find(key.c_str(), obj); Json copy_json = val.copy(); if (find == nullptr) { json_object_append(obj, key.c_str(), 0, copy_json.node_); copy_json.reset(); return; } json_object_insert_before(find, obj, key.c_str(), 0, copy_json.node_); copy_json.reset(); json_value_t* remove_val = json_object_remove(find, obj); json_value_destroy(remove_val); } void normal_push_back(const std::string& key, bool val); void normal_push_back(const std::string& key, std::nullptr_t val); void normal_push_back(const std::string& key, const std::string& val); void normal_push_back(const std::string& key, const char* val); void normal_push_back(const std::string& key, const std::vector& val); void normal_push_back(const std::string& key, const Json& val); public: void erase(const std::string& key); template ::value, bool>::type = true> void push_back(T val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); json_array_append(arr, JSON_VALUE_NUMBER, static_cast(val)); } template ::value || std::is_same::value, bool>::type = true> void push_back(const T& val) { if (is_placeholder()) { *this = Json::Array{{val}}; return; } if (!can_arr_push_back()) { return; } json_array_t* arr = json_value_array(node_); Json copy_json = val.copy(); json_array_append(arr, 0, copy_json.node_); copy_json.reset(); } void push_back(bool val); void push_back(std::nullptr_t val); void push_back(const std::string& val); void push_back(const char* val); void push_back(const std::vector& val); void push_back(const std::initializer_list& val) { push_back(std::vector(val)); } void push_back(const Json& val); void erase(int index); private: template ::value, bool>::type = true> void update_arr(T val) { json_array_t* arr = json_value_array(parent_); json_array_insert_before(node_, arr, JSON_VALUE_NUMBER, static_cast(val)); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } template ::value || std::is_same::value, bool>::type = true> void update_arr(const T& val) { json_array_t* arr = json_value_array(parent_); Json copy_json = val.copy(); json_array_insert_before(node_, arr, 0, copy_json.node_); copy_json.reset(); json_value_t* remove_val = json_array_remove(node_, arr); json_value_destroy(remove_val); } void update_arr(bool val); void update_arr(std::nullptr_t val); void update_arr(const std::string& val); void update_arr(const char* val); void update_arr(const std::vector& val); void update_arr(const Json& val); public: class IteratorBase { public: friend class Json; explicit IteratorBase(const json_value_t* val) : val_(val), json_(new Json(nullptr, nullptr, "")) { } ~IteratorBase() { if (json_ != nullptr) { delete json_; } } IteratorBase(const IteratorBase& iter) { val_ = iter.val_; key_ = iter.key_; json_ = new Json(iter.json_->node_, iter.json_->parent_, iter.json_->parent_key_); } IteratorBase& operator=(const IteratorBase& iter) { if (this == &iter) { return *this; } val_ = iter.val_; key_ = iter.key_; json_->reset(iter.json_->node_, iter.json_->parent_, false, iter.json_->parent_key_); return *this; } IteratorBase(IteratorBase&& iter) : val_(iter.val_), key_(iter.key_), json_(iter.json_) { iter.val_ = nullptr; iter.key_ = nullptr; iter.json_ = nullptr; } IteratorBase& operator=(IteratorBase&& iter) { if (this == &iter) { return *this; } val_ = iter.val_; iter.val_ = nullptr; key_ = iter.key_; iter.key_ = nullptr; delete json_; json_ = iter.json_; iter.json_ = nullptr; return *this; } Json& operator*() const { if (key_ != nullptr) { json_->parent_key_ = std::string(key_); } else { json_->parent_key_ = ""; } return *json_; } Json* operator->() const { if (key_ != nullptr) { json_->parent_key_ = std::string(key_); } else { json_->parent_key_ = ""; } return json_; } bool operator==(const IteratorBase& other) const { return json_->node_ == other.json_->node_; } bool operator!=(IteratorBase const& other) const { return !(*this == other); } private: const json_value_t* val_ = nullptr; const char* key_ = nullptr; Json* json_; // cursor }; class iterator : public IteratorBase { public: friend class Json; explicit iterator(const json_value_t* val) : IteratorBase(val) { } iterator& operator++() { if (is_end()) { return *this; } forward(); return *this; } iterator operator++(int) { iterator old = (*this); if (!is_end()) { ++(*this); } return old; } private: void set_begin() { json_->node_ = nullptr; key_ = nullptr; forward(); } void set_end() { json_->node_ = nullptr; key_ = nullptr; } bool is_end() { return json_->node_ == nullptr && key_ == nullptr; } void forward() { if (json_value_type(val_) == JSON_VALUE_OBJECT) { json_object_t* obj = json_value_object(val_); key_ = json_object_next_name(key_, obj); json_->node_ = const_cast( json_object_next_value(json_->node_, obj)); } else if (json_value_type(val_) == JSON_VALUE_ARRAY) { json_array_t* arr = json_value_array(val_); json_->node_ = const_cast( json_array_next_value(json_->node_, arr)); } } }; class reverse_iterator : public IteratorBase { public: friend class Json; explicit reverse_iterator(const json_value_t* val) : IteratorBase(val) { } reverse_iterator& operator++() { if (is_rend()) { return *this; } backward(); return *this; } reverse_iterator operator++(int) { reverse_iterator old = (*this); if (!is_rend()) { ++(*this); } return old; } private: void set_rbegin() { json_->node_ = nullptr; key_ = nullptr; backward(); } void set_rend() { json_->node_ = nullptr; key_ = nullptr; } bool is_rend() { return json_->node_ == nullptr && key_ == nullptr; } void backward() { if (json_value_type(val_) == JSON_VALUE_OBJECT) { json_object_t* obj = json_value_object(val_); key_ = json_object_prev_name(key_, obj); json_->node_ = const_cast( json_object_prev_value(json_->node_, obj)); } else if (json_value_type(val_) == JSON_VALUE_ARRAY) { json_array_t* arr = json_value_array(val_); json_->node_ = const_cast( json_array_prev_value(json_->node_, arr)); } } }; iterator begin() const { iterator iter(node_); iter.set_begin(); return iter; } iterator end() const { iterator iter(node_); iter.set_end(); return iter; } reverse_iterator rbegin() const { reverse_iterator iter(node_); iter.set_rbegin(); return iter; } reverse_iterator rend() const { reverse_iterator iter(node_); iter.set_rend(); return iter; } private: bool can_obj_push_back(); bool can_arr_push_back(); // can convert to any type, just like a placeholder bool is_placeholder() const { return is_null() && parent_ != nullptr; } void destroy_node(json_value_t** node) { if (allocated_) { json_value_destroy(*node); *node = nullptr; allocated_ = false; } } void copy(const Json& other); public: // Constructors for the various types of JSON value. // Basic json type Constructors Json(); Json(const std::string& str); Json(const char* str); Json(std::nullptr_t null); template ::value, bool>::type = true> Json(T val) : node_(json_value_create(JSON_VALUE_NUMBER, static_cast(val))), parent_(nullptr), allocated_(true) { } Json(bool val); Json(const std::vector& val); // For parse Json(const std::string& str, bool parse_flag); Json(const Json& json); Json& operator=(const Json& json); Json(Json&& other); Json& operator=(Json&& other); ~Json(); protected: // watcher Json(const json_value_t* node, const json_value_t* parent, std::string&& key); Json(const json_value_t* node, const json_value_t* parent, const std::string& key); enum class JsonType { Object, Array, }; // For Object && Array Construct Json(JsonType type); bool is_root() const { return parent_ == nullptr; } bool to_object(); bool to_array(); void reset() { node_ = nullptr; parent_ = nullptr; allocated_ = false; parent_key_.clear(); } void reset(json_value_t* node, const json_value_t* parent, bool allocated, const std::string& parent_key) { node_ = node; parent_ = parent; allocated_ = allocated; parent_key_ = parent_key; } void reset(json_value_t* node, const json_value_t* parent, bool allocated, std::string&& parent_key) { node_ = node; parent_ = parent; allocated_ = allocated; parent_key_ = std::move(parent_key); } void set_parent(const json_value_t* parent, std::string&& parent_key) { parent_ = parent; parent_key_ = std::move(parent_key); } private: json_value_t* node_ = nullptr; const json_value_t* parent_ = nullptr; bool allocated_ = false; std::string parent_key_; private: // for parse static void value_convert(const json_value_t* val, int spaces, int depth, std::string* out_str); static void string_convert(const char* raw_str, std::string* out_str); static void number_convert(double number, std::string* out_str); static void array_convert(const json_array_t* arr, int spaces, int depth, std::string* out_str); static void array_convert_not_format(const json_array_t* arr, std::string* out_str); static void object_convert(const json_object_t* obj, int spaces, int depth, std::string* out_str); static void object_convert_not_format(const json_object_t* obj, std::string* out_str); friend inline std::ostream& operator<<(std::ostream& os, const Json& json) { return (os << json.dump()); } }; class Object_S : public Json { public: Object_S() : Json(JsonType::Object) { } Object_S(const json_value_t* node, const json_value_t* parent) : Json(node, parent, "") { } using string_type = std::basic_string, std::allocator>; using pair_type = std::pair; Object_S(std::initializer_list list) { std::for_each(list.begin(), list.end(), [this](const pair_type& pair) { this->push_back(pair.first, pair.second); }); } }; class Array_S : public Json { public: Array_S() : Json(JsonType::Array) { } Array_S(const json_value_t* node, const json_value_t* parent) : Json(node, parent, "") { } Array_S(std::initializer_list list) { std::for_each(list.begin(), list.end(), [this](const Json& js) { this->push_back(js); }); } }; template typename std::enable_if::value, T>::type Json::get() const { return Json::Object(node_, parent_); } template typename std::enable_if::value, T>::type Json::get() const { return Json::Array(node_, parent_); } } // namespace wfrest #endif // WFREST_JSON_H_ sogou-srpc-ac65014/tools/templates/config/config_full.cc000066400000000000000000000410321525217424400233470ustar00rootroot00000000000000#include #include "config.h" #include "workflow/WFGlobal.h" #include "workflow/UpstreamManager.h" #include "workflow/UpstreamPolicies.h" #include "srpc/rpc_metrics_filter.h" #include "srpc/rpc_trace_filter.h" using namespace srpc; // default upstream_route_t static unsigned int default_consistent_hash(const char *path, const char *query, const char *fragment) { return 0; } static unsigned int default_select_route(const char *path, const char *query, const char *fragment) { return 0; } static void set_endpoint_params(const wfrest::Json& data, struct EndpointParams *params) { *params = ENDPOINT_PARAMS_DEFAULT; for (const auto& it : data) { if (it.key() == "max_connections") params->max_connections = data["max_connections"]; else if (it.key() == "connect_timeout") params->connect_timeout = data["connect_timeout"]; else if (it.key() == "response_timeout") params->response_timeout = data["response_timeout"]; else if (it.key() == "ssl_connect_timeout") params->ssl_connect_timeout = data["ssl_connect_timeout"]; else if (it.key() == "use_tls_sni") params->use_tls_sni = data["use_tls_sni"]; else { printf("[INFO][set_endpoint_params] Unknown key : %s\n", it.key().c_str()); } } } static void load_global(const wfrest::Json& data) { struct WFGlobalSettings settings = GLOBAL_SETTINGS_DEFAULT; std::string resolv_conf_path; std::string hosts_path; for (const auto& it : data) { if (it.key() == "endpoint_params") { set_endpoint_params(data["endpoint_params"], &settings.endpoint_params); } else if (it.key() == "dns_server_params") { set_endpoint_params(data["dns_server_params"], &settings.dns_server_params); } else if (it.key() == "dns_ttl_default") settings.dns_ttl_default = data["dns_ttl_default"]; else if (it.key() == "dns_ttl_min") settings.dns_ttl_min = data["dns_ttl_min"]; else if (it.key() == "dns_threads") settings.dns_threads = data["dns_threads"]; else if (it.key() == "poller_threads") settings.poller_threads = data["poller_threads"]; else if (it.key() == "handler_threads") settings.handler_threads = data["handler_threads"]; else if (it.key() == "compute_threads") settings.compute_threads = data["compute_threads"]; else if (it.key() == "resolv_conf_path") { resolv_conf_path = data["resolv_conf_path"].get(); settings.resolv_conf_path = resolv_conf_path.c_str(); } else if (it.key() == "hosts_path") { hosts_path = data["hosts_path"].get(); settings.hosts_path = hosts_path.c_str(); } else printf("[INFO][load_global] Unknown key : %s\n", it.key().c_str()); } WORKFLOW_library_init(&settings); } static bool load_upstream_server(const wfrest::Json& data, std::vector& hosts, std::vector& params) { AddressParams param; hosts.clear(); params.clear(); for (const auto& server : data) { if (server.has("host") == false) { printf("[ERROR][load_upstream] Invalid upstream server\n"); continue; } param = ADDRESS_PARAMS_DEFAULT; if (server.has("params")) { for (const auto& p : server["params"]) { if (p.key() == "endpoint_params") set_endpoint_params(p.value(), ¶m.endpoint_params); else if (p.key() == "weight") param.weight = p.value().get(); else if (p.key() == "max_fails") param.max_fails = p.value().get(); else if (p.key() == "dns_ttl_default") param.dns_ttl_default = p.value().get(); else if (p.key() == "dns_ttl_min") param.dns_ttl_min = p.value().get(); else if (p.key() == "server_type") param.server_type = p.value().get(); else if (p.key() == "group_id") param.group_id = p.value().get(); else printf("[ERROR][load_upstream] Invalid params: %s\n", p.key().c_str()); } } hosts.push_back(server["host"]); params.push_back(param); } if (hosts.size() == 0) return false; else return true; } static void load_upstream(const wfrest::Json& data) { std::string name; std::string type; bool try_another; std::vector hosts; std::vector params; for (const auto& it : data) { if (it.has("name") == false || it.has("type") == false || it.has("server") == false || load_upstream_server(it["server"], hosts, params) == false) { printf("[ERROR][load_upstream] Invalid upstream\n"); continue; } name = it["name"].get(); type = it["type"].get(); if (it.has("try_another")) try_another = it["try_another"]; else try_another = false; if (type == "weighted_random") { UpstreamManager::upstream_create_weighted_random(name, try_another); } else if (type == "consistent_hash") { UpstreamManager::upstream_create_consistent_hash(name, default_consistent_hash); } else if (type == "round_robin") { UpstreamManager::upstream_create_round_robin(name, try_another); } else if (type == "manual") { UpstreamManager::upstream_create_manual(name, default_select_route, try_another, default_consistent_hash); } else if (type == "vnswrr") { UpstreamManager::upstream_create_vnswrr(name); } else { printf("[INFO][load_upstream] Unknown type : %s\n", type.c_str()); continue; } for (size_t i = 0; i < hosts.size(); i++) UpstreamManager::upstream_add_server(name, hosts[i], ¶ms[i]); } } void RPCConfig::load_server() { if (this->data["server"].has("port")) this->s_port = this->data["server"]["port"]; if (this->data["server"].has("root")) this->root_path = this->data["server"]["root"].get(); if (this->data["server"].has("cert_file")) this->s_cert_file = this->data["server"]["cert_file"].get(); if (this->data["server"].has("file_key")) this->s_file_key = this->data["server"]["file_key"].get(); if (this->data["server"].has("error_page")) { for (const auto& it : this->data["server"]["error_page"]) { std::string page; if (it.has("error") == true && it.has("error") == true) { page = it["page"].get(); for (const auto& e : it["error"]) this->error_page.insert(std::make_pair(e.get(), page)); } else { printf("[ERROR][load_file_service] Invalid error_page\n"); continue; } } } } void RPCConfig::load_client() { if (this->data["client"].has("transport_type")) { std::string type = this->data["client"]["transport_type"].get(); if (type == "TT_SCTP") this->c_transport_type = TT_SCTP; else if (type == "TT_UDP") this->c_transport_type = TT_UDP; } if (this->data["client"].has("remote_host")) this->c_host = this->data["client"]["remote_host"].get(); if (this->data["client"].has("remote_port")) this->c_port = this->data["client"]["remote_port"]; if (this->data["client"].has("is_ssl")) this->c_is_ssl = this->data["client"]["is_ssl"]; if (this->data["client"].has("callee_timeout")) this->c_callee_timeout = this->data["client"]["callee_timeout"]; if (this->data["client"].has("redirect_max")) this->c_redirect_max = this->data["client"]["redirect_max"]; if (this->data["client"].has("retry_max")) this->c_retry_max = this->data["client"]["retry_max"]; if (this->data["client"].has("user_name")) this->c_user_name = this->data["client"]["user_name"].get(); if (this->data["client"].has("password")) this->c_password = this->data["client"]["password"].get(); } bool RPCConfig::load(const char *file) { FILE *fp = fopen(file, "r"); if (!fp) return false; this->data = wfrest::Json::parse(fp); fclose(fp); if (this->data.is_valid() == false) return false; for (const auto& it : this->data) { if (it.key() == "server") this->load_server(); else if (it.key() == "client") this->load_client(); else if (it.key() == "global") load_global(it.value()); else if (it.key() == "upstream") load_upstream(it.value()); else if (it.key() == "metrics") this->load_metrics(); else if (it.key() == "trace") this->load_trace(); else printf("[INFO][RPCConfig::load] Unknown key: %s\n", it.key().c_str()); } return true; }; void RPCConfig::load_metrics() { for (const auto& it : this->data["metrics"]) { if (it.has("filter") == false) continue; std::string filter_name = it["filter"]; if (filter_name.compare("prometheus") == 0) { if (it.has("port") == false) continue; RPCMetricsPull *filter = new RPCMetricsPull(); unsigned short port = it["port"]; filter->init(port); this->filters.push_back(filter); } else if (filter_name.compare("opentelemetry") == 0) { if (it.has("address") == false) continue; std::string name = it["filter_name"]; std::string url = it["address"]; unsigned int redirect_max = OTLP_HTTP_REDIRECT_MAX; unsigned int retry_max = OTLP_HTTP_RETRY_MAX; size_t report_threshold = RPC_REPORT_THREHOLD_DEFAULT; size_t report_interval = RPC_REPORT_INTERVAL_DEFAULT; if (it.has("redirect_max")) redirect_max = it["redirect_max"]; if (it.has("retry_max")) retry_max = it["retry_max"]; if (it.has("report_threshold")) report_threshold = it["report_threshold"]; if (it.has("report_interval_ms")) report_interval = it["report_interval_ms"]; RPCMetricsOTel *filter = new RPCMetricsOTel(name, url, redirect_max, retry_max, report_threshold, report_interval); if (it.has("attributes")) { for (const auto& kv : it["attributes"]) { if (kv.has("key") == false || kv.has("value") == false) continue; filter->add_attributes(kv["key"], kv["value"]); } } this->filters.push_back(filter); } else { printf("[ERROR][RPCConfig::load_metrics] Unknown metrics: %s\n", filter_name.c_str()); } } } void RPCConfig::load_trace() { for (const auto& it : this->data["trace"]) { if (it.has("filter") == false) continue; std::string filter_name = it["filter"]; size_t spans_per_second = SPANS_PER_SECOND_DEFAULT; if (filter_name.compare("default") == 0) { if (it.has("spans_per_second")) spans_per_second = it["spans_per_second"]; auto *filter = new RPCTraceDefault(spans_per_second); this->filters.push_back(filter); } else if (filter_name.compare("opentelemetry") == 0) { if (it.has("address") == false) continue; std::string url = it["address"]; unsigned int redirect_max = OTLP_HTTP_REDIRECT_MAX; unsigned int retry_max = OTLP_HTTP_RETRY_MAX; size_t report_threshold = RPC_REPORT_THREHOLD_DEFAULT; size_t report_interval = RPC_REPORT_INTERVAL_DEFAULT; if (it.has("redirect_max")) redirect_max = it["redirect_max"]; if (it.has("retry_max")) retry_max = it["retry_max"]; if (it.has("report_threshold")) report_threshold = it["report_threshold"]; if (it.has("report_interval_ms")) report_interval = it["report_interval_ms"]; auto *filter = new RPCTraceOpenTelemetry(url, OTLP_TRACES_PATH, redirect_max, retry_max, spans_per_second, report_threshold, report_interval); if (it.has("attributes")) { for (const auto& kv : it["attributes"]) { if (kv.has("key") == false || kv.has("value") == false) continue; filter->add_attributes(kv["key"], kv["value"]); } } this->filters.push_back(filter); } else { printf("[ERROR][RPCConfig::load_metrics] Unknown metrics: %s\n", filter_name.c_str()); } } } void RPCConfig::load_filter(SRPCServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(SRPCClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(SRPCHttpServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(SRPCHttpClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(BRPCServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(BRPCClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(ThriftServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(ThriftClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(ThriftHttpServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(ThriftHttpClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(TRPCServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(TRPCClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } void RPCConfig::load_filter(TRPCHttpServer& server) { for (auto *filter : this->filters) server.add_filter(filter); } void RPCConfig::load_filter(TRPCHttpClient& client) { for (auto *filter : this->filters) client.add_filter(filter); } RPCConfig::~RPCConfig() { for (size_t i = 0; i < this->filters.size(); i++) delete this->filters[i]; } sogou-srpc-ac65014/tools/templates/config/config_full.h000066400000000000000000000055101525217424400232120ustar00rootroot00000000000000#ifndef _RPC_CONFIG_H_ #define _RPC_CONFIG_H_ #include #include #include #include "Json.h" #include "srpc/rpc_types.h" #include "srpc/rpc_define.h" #include "srpc/rpc_filter.h" namespace srpc { class RPCConfig { public: using ErrorPageMap = std::unordered_map; bool load(const char *file); void load_filter(SRPCServer& server); void load_filter(SRPCClient& client); void load_filter(SRPCHttpServer& server); void load_filter(SRPCHttpClient& client); void load_filter(BRPCServer& server); void load_filter(BRPCClient& client); void load_filter(ThriftServer& server); void load_filter(ThriftClient& client); void load_filter(ThriftHttpServer& server); void load_filter(ThriftHttpClient& client); void load_filter(TRPCServer& server); void load_filter(TRPCClient& client); void load_filter(TRPCHttpServer& server); void load_filter(TRPCHttpClient& client); unsigned short server_port() const { return this->s_port; } const char *server_cert_file() const { return this->s_cert_file.c_str(); } const char *server_file_key() const { return this->s_file_key.c_str(); } enum TransportType client_transport_type() const { return this->c_transport_type; } const char *client_host() const { return this->c_host.c_str(); } unsigned short client_port() const { return this->c_port; } bool client_is_ssl() const { return this->c_is_ssl; } const char *client_url() const { return this->c_url.c_str(); } int client_callee_timeout() const { return this->c_callee_timeout; } const char *client_caller() const { return this->c_caller.c_str(); } int redirect_max() const { return this->c_redirect_max; } int retry_max() const { return this->c_retry_max; } const char *client_user_name() const { return this->c_user_name.c_str(); } const char *client_password() const { return this->c_password.c_str(); } const char *get_root_path() const { return this->root_path.c_str(); } const ErrorPageMap& get_error_page() const { return this->error_page; } public: RPCConfig() : s_port(0), c_transport_type(TT_TCP), c_port(0), c_is_ssl(false), c_redirect_max(0), c_retry_max(0) { } ~RPCConfig(); private: void load_server(); void load_client(); void load_metrics(); void load_trace(); wfrest::Json data; std::vector filters; unsigned short s_port; std::string s_cert_file; std::string s_file_key; enum TransportType c_transport_type; std::string c_host; unsigned short c_port; bool c_is_ssl; std::string c_url; int c_callee_timeout; std::string c_caller; int c_redirect_max; int c_retry_max; std::string c_user_name; std::string c_password; std::string root_path; ErrorPageMap error_page; }; } #endif sogou-srpc-ac65014/tools/templates/config/config_simple.cc000066400000000000000000000224631525217424400237050ustar00rootroot00000000000000#include #include "config.h" #include "workflow/WFGlobal.h" #include "workflow/UpstreamManager.h" #include "workflow/UpstreamPolicies.h" using namespace srpc; // default upstream_route_t static unsigned int default_consistent_hash(const char *path, const char *query, const char *fragment) { return 0; } static unsigned int default_select_route(const char *path, const char *query, const char *fragment) { return 0; } static void set_endpoint_params(const wfrest::Json& data, struct EndpointParams *params) { *params = ENDPOINT_PARAMS_DEFAULT; for (const auto& it : data) { if (it.key() == "max_connections") params->max_connections = data["max_connections"]; else if (it.key() == "connect_timeout") params->connect_timeout = data["connect_timeout"]; else if (it.key() == "response_timeout") params->response_timeout = data["response_timeout"]; else if (it.key() == "ssl_connect_timeout") params->ssl_connect_timeout = data["ssl_connect_timeout"]; else if (it.key() == "use_tls_sni") params->use_tls_sni = data["use_tls_sni"]; else { printf("[INFO][set_endpoint_params] Unknown key : %s\n", it.key().c_str()); } } } static void load_global(const wfrest::Json& data) { struct WFGlobalSettings settings = GLOBAL_SETTINGS_DEFAULT; std::string resolv_conf_path; std::string hosts_path; for (const auto& it : data) { if (it.key() == "endpoint_params") { set_endpoint_params(data["endpoint_params"], &settings.endpoint_params); } else if (it.key() == "dns_server_params") { set_endpoint_params(data["dns_server_params"], &settings.dns_server_params); } else if (it.key() == "dns_ttl_default") settings.dns_ttl_default = data["dns_ttl_default"]; else if (it.key() == "dns_ttl_min") settings.dns_ttl_min = data["dns_ttl_min"]; else if (it.key() == "dns_threads") settings.dns_threads = data["dns_threads"]; else if (it.key() == "poller_threads") settings.poller_threads = data["poller_threads"]; else if (it.key() == "handler_threads") settings.handler_threads = data["handler_threads"]; else if (it.key() == "compute_threads") settings.compute_threads = data["compute_threads"]; else if (it.key() == "resolv_conf_path") { resolv_conf_path = data["resolv_conf_path"].get(); settings.resolv_conf_path = resolv_conf_path.c_str(); } else if (it.key() == "hosts_path") { hosts_path = data["hosts_path"].get(); settings.hosts_path = hosts_path.c_str(); } else printf("[INFO][load_global] Unknown key : %s\n", it.key().c_str()); } WORKFLOW_library_init(&settings); } static bool load_upstream_server(const wfrest::Json& data, std::vector& hosts, std::vector& params) { AddressParams param; hosts.clear(); params.clear(); for (const auto& server : data) { if (server.has("host") == false) { printf("[ERROR][load_upstream] Invalid upstream server\n"); continue; } param = ADDRESS_PARAMS_DEFAULT; if (server.has("params")) { for (const auto& p : server["params"]) { if (p.key() == "endpoint_params") set_endpoint_params(p.value(), ¶m.endpoint_params); else if (p.key() == "weight") param.weight = p.value().get(); else if (p.key() == "max_fails") param.max_fails = p.value().get(); else if (p.key() == "dns_ttl_default") param.dns_ttl_default = p.value().get(); else if (p.key() == "dns_ttl_min") param.dns_ttl_min = p.value().get(); else if (p.key() == "server_type") param.server_type = p.value().get(); else if (p.key() == "group_id") param.group_id = p.value().get(); else printf("[ERROR][load_upstream] Invalid params: %s\n", p.key().c_str()); } } hosts.push_back(server["host"]); params.push_back(param); } if (hosts.size() == 0) return false; else return true; } static void load_upstream(const wfrest::Json& data) { std::string name; std::string type; bool try_another; std::vector hosts; std::vector params; for (const auto& it : data) { if (it.has("name") == false || it.has("type") == false || it.has("server") == false || load_upstream_server(it["server"], hosts, params) == false) { printf("[ERROR][load_upstream] Invalid upstream\n"); continue; } name = it["name"].get(); type = it["type"].get(); if (it.has("try_another")) try_another = it["try_another"]; else try_another = false; if (type == "weighted_random") { UpstreamManager::upstream_create_weighted_random(name, try_another); } else if (type == "consistent_hash") { UpstreamManager::upstream_create_consistent_hash(name, default_consistent_hash); } else if (type == "round_robin") { UpstreamManager::upstream_create_round_robin(name, try_another); } else if (type == "manual") { UpstreamManager::upstream_create_manual(name, default_select_route, try_another, default_consistent_hash); } else if (type == "vnswrr") { UpstreamManager::upstream_create_vnswrr(name); } else { printf("[INFO][load_upstream] Unknown type : %s\n", type.c_str()); continue; } for (size_t i = 0; i < hosts.size(); i++) UpstreamManager::upstream_add_server(name, hosts[i], ¶ms[i]); } } void RPCConfig::load_server() { if (this->data["server"].has("port")) this->s_port = this->data["server"]["port"]; if (this->data["server"].has("root")) this->root_path = this->data["server"]["root"].get(); if (this->data["server"].has("cert_file")) this->s_cert_file = this->data["server"]["cert_file"].get(); if (this->data["server"].has("file_key")) this->s_file_key = this->data["server"]["file_key"].get(); if (this->data["server"].has("error_page")) { for (const auto& it : this->data["server"]["error_page"]) { std::string page; if (it.has("error") == true && it.has("error") == true) { page = it["page"].get(); for (const auto& e : it["error"]) this->error_page.insert(std::make_pair(e.get(), page)); } else { printf("[ERROR][load_file_service] Invalid error_page\n"); continue; } } } } void RPCConfig::load_client() { if (this->data["client"].has("remote_host")) this->c_host = this->data["client"]["remote_host"].get(); if (this->data["client"].has("remote_port")) this->c_port = this->data["client"]["remote_port"]; if (this->data["client"].has("redirect_max")) this->c_redirect_max = this->data["client"]["redirect_max"]; if (this->data["client"].has("retry_max")) this->c_retry_max = this->data["client"]["retry_max"]; if (this->data["client"].has("user_name")) this->c_user_name = this->data["client"]["user_name"].get(); if (this->data["client"].has("password")) this->c_password = this->data["client"]["password"].get(); } bool RPCConfig::load(const char *file) { FILE *fp = fopen(file, "r"); if (!fp) return false; this->data = wfrest::Json::parse(fp); fclose(fp); if (this->data.is_valid() == false) return false; for (const auto& it : this->data) { if (it.key() == "server") this->load_server(); else if (it.key() == "client") this->load_client(); else if (it.key() == "global") load_global(it.value()); else if (it.key() == "upstream") load_upstream(it.value()); else printf("[INFO][RPCConfig::load] Unknown key: %s\n", it.key().c_str()); } return true; }; RPCConfig::~RPCConfig() { } sogou-srpc-ac65014/tools/templates/config/config_simple.h000066400000000000000000000030041525217424400235350ustar00rootroot00000000000000#ifndef _RPC_CONFIG_H_ #define _RPC_CONFIG_H_ #include #include #include #include "Json.h" namespace srpc { class RPCConfig { public: using ErrorPageMap = std::unordered_map; bool load(const char *file); unsigned short server_port() const { return this->s_port; } const char *server_cert_file() const { return this->s_cert_file.c_str(); } const char *server_file_key() const { return this->s_file_key.c_str(); } unsigned short client_port() const { return this->c_port; } const char *client_host() const { return this->c_host.c_str(); } int redirect_max() const { return this->c_redirect_max; } int retry_max() const { return this->c_retry_max; } const char *client_user_name() const { return this->c_user_name.c_str(); } const char *client_password() const { return this->c_password.c_str(); } const char *get_root_path() const { return this->root_path.c_str(); } const ErrorPageMap& get_error_page() const { return this->error_page; } public: RPCConfig() : s_port(0), c_port(0), c_redirect_max(0), c_retry_max(0) { } ~RPCConfig(); private: void load_server(); void load_client(); wfrest::Json data; unsigned short s_port; std::string s_cert_file; std::string s_file_key; std::string c_host; unsigned short c_port; int c_redirect_max; int c_retry_max; std::string c_user_name; std::string c_password; std::string root_path; ErrorPageMap error_page; }; } #endif sogou-srpc-ac65014/tools/templates/file/000077500000000000000000000000001525217424400202235ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/file/404.html000066400000000000000000000000731525217424400214200ustar00rootroot00000000000000This is not the web page you are looking for. sogou-srpc-ac65014/tools/templates/file/50x.html000066400000000000000000000010501525217424400215210ustar00rootroot00000000000000 Error

An error occurred.

Sorry, the page you are looking for is currently unavailable.
Please try again later.

If you are the system administrator of this resource then you should check the srpc-ctl README.md for details.

Faithfully yours, srpc.

sogou-srpc-ac65014/tools/templates/file/file_service.cc000066400000000000000000000061321525217424400231730ustar00rootroot00000000000000#include "file_service.h" SubTask *FileService::create_error_task(int code, struct FileService::ModuleCtx *ctx) { if (ctx->is_error_set == false) { ctx->resp->set_status_code(std::to_string(code)); ctx->is_error_set = true; const auto it = this->error_page.find(code); if (it != this->error_page.end()) return this->create_file_task(it->second, ctx); } return WFTaskFactory::create_empty_task(); } void FileService::pread_callback(WFFileIOTask *task) { FileIOArgs *args = task->get_args(); long ret = task->get_retval(); struct ModuleCtx *ctx = (struct ModuleCtx *)series_of(task)->get_context(); protocol::HttpResponse *resp = ctx->resp; if (task->get_state() == WFT_STATE_SUCCESS) { resp->append_output_body_nocopy(args->buf, ret); } else { auto *error_task = this->create_error_task(503, ctx); series_of(task)->push_back(error_task); } } SubTask *FileService::create_file_task(const std::string& path, struct FileService::ModuleCtx *ctx) { SubTask *task; struct stat st; std::string abs_path = this->root + path; if (abs_path.back() == '/') abs_path += "index.html"; if (stat(abs_path.c_str(), &st) >= 0) { size_t size = st.st_size; void *buf = malloc(size); if (buf) { ctx->buf = buf; auto&& cb = std::bind(&FileService::pread_callback, this, std::placeholders::_1); task = WFTaskFactory::create_pread_task(abs_path, buf, size, 0, cb); } else { task = this->create_error_task(503, ctx); } } else { task = this->create_error_task(404, ctx); } return task; } WFModuleTask *FileService::create_module(WFHttpTask *server_task, const std::string& path) { fprintf(stderr, "file service get request: %s\n", path.c_str()); struct ModuleCtx *ctx = new ModuleCtx(server_task->get_resp()); SubTask *next = this->create_file_task(path, ctx); WFModuleTask *module = WFTaskFactory::create_module_task(next, [server_task](const WFModuleTask *mod) { struct ModuleCtx *ctx; ctx = (struct ModuleCtx *)mod->sub_series()->get_context(); void *buf = ctx->buf; server_task->set_callback([buf](WFHttpTask *t){ free(buf); }); delete ctx; }); module->sub_series()->set_context(ctx); return module; } void FileService::process(WFHttpTask *server_task) { protocol::HttpRequest *req = server_task->get_req(); protocol::HttpResponse *resp = server_task->get_resp(); std::string path = req->get_request_uri(); auto pos = path.find_first_of('?'); if (pos != std::string::npos) path = path.substr(0, pos); resp->add_header_pair("Server", "SRPC HTTP File Server"); WFModuleTask *module = this->create_module(server_task, path); series_of(server_task)->push_back(module); } sogou-srpc-ac65014/tools/templates/file/file_service.h000066400000000000000000000025731525217424400230420ustar00rootroot00000000000000#ifndef _RPC_FILE_SERVICE_H_ #define _RPC_FILE_SERVICE_H_ #include #include #include #include "workflow/Workflow.h" #include "workflow/HttpMessage.h" #include "workflow/HttpUtil.h" #include "workflow/WFHttpServer.h" #include "workflow/WFFacilities.h" // This is a simple exmaple for file service class FileService { public: using ErrorPageMap = std::unordered_map; void process(WFHttpTask *server_task); void pread_callback(WFFileIOTask *task); protected: struct ModuleCtx { protocol::HttpResponse *resp; void *buf; bool is_error_set; ModuleCtx(protocol::HttpResponse * resp) : resp(resp), buf(NULL), is_error_set(false) { } }; private: WFModuleTask *create_module(WFHttpTask *task, const std::string& path); SubTask *create_file_task(const std::string& path, struct ModuleCtx *ctx); SubTask *create_error_task(int code, struct ModuleCtx *ctx); public: FileService(std::string root, const ErrorPageMap& error_page) : root(std::move(root)), error_page(error_page) { if (this->root.empty()) root = "./"; else if (this->root.at(this->root.length() - 1) != '/') root += "/"; } private: std::string root; const ErrorPageMap& error_page; }; #endif sogou-srpc-ac65014/tools/templates/file/index.html000066400000000000000000000000671525217424400222230ustar00rootroot00000000000000Hello from workflow and srpc file server! sogou-srpc-ac65014/tools/templates/file/server.conf000066400000000000000000000003721525217424400224020ustar00rootroot00000000000000{ "server": { "port": 8080, "root": "./html/", "error_page" : [ { "error" : [ 404 ], "page" : "404.html" }, { "error" : [ 500, 502, 503, 504], "page" : "50x.html" } ] } } sogou-srpc-ac65014/tools/templates/file/server_main.cc000066400000000000000000000025021525217424400230430ustar00rootroot00000000000000#include #include #include "workflow/HttpMessage.h" #include "workflow/HttpUtil.h" #include "workflow/WFHttpServer.h" #include "workflow/WFFacilities.h" #include "config/config.h" #include "file_service.h" using namespace protocol; static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./server.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); } int main() { init(); unsigned short port = config.server_port(); const char *cert_file = config.server_cert_file(); const char *file_key = config.server_file_key(); FileService service(config.get_root_path(), config.get_error_page()); auto&& proc = std::bind(&FileService::process, &service, std::placeholders::_1); int ret; WFHttpServer server(proc); if (strlen(cert_file) != 0 && strlen(file_key) != 0) ret = server.start(port, cert_file, file_key); else ret = server.start(port); if (ret == 0) { fprintf(stderr, "http file service start, port %u\n", port); wait_group.wait(); server.stop(); } else perror("http file service start"); return 0; } sogou-srpc-ac65014/tools/templates/proxy/000077500000000000000000000000001525217424400204655ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/proxy/client_rpc.conf000066400000000000000000000002301525217424400234510ustar00rootroot00000000000000{ "client": { "remote_host": "127.0.0.1", "remote_port": 1411, "is_ssl" : false, "retry_max": 1, "callee" : "rpc_client" } } sogou-srpc-ac65014/tools/templates/proxy/proxy.conf000066400000000000000000000002101525217424400225060ustar00rootroot00000000000000{ "server": { "port": %s }, "client": { "remote_host": "127.0.0.1", "remote_port": %s, "retry_max": 2 } } sogou-srpc-ac65014/tools/templates/proxy/proxy_main.cc000066400000000000000000000043411525217424400231630ustar00rootroot00000000000000#include #include #include "workflow/WFTaskFactory.h" #include "workflow/WF%sServer.h" #include "workflow/WFFacilities.h" #include "config/config.h" #include "config/util.h" static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./proxy.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } void callback(WF%sTask *client_task) { int state = client_task->get_state(); int error = client_task->get_error(); SeriesWork *series = series_of(client_task); protocol::%sResponse *resp = client_task->get_resp(); protocol::%sResponse *proxy_resp = (protocol::%sResponse *)series->get_context(); // Copy the remote server's response, to proxy response. if (state == WFT_STATE_SUCCESS)%s fprintf(stderr, "backend server state = %%d error = %%d. response client.\n", state, error); } void process(WF%sTask *server_task) { protocol::%sRequest *req = server_task->get_req(); std::string backend_server = config.client_host(); unsigned short backend_server_port = config.client_port(); std::string url = std::string("%s://") + backend_server + std::string(":") + std::to_string(backend_server_port); WF%sTask *client_task = WFTaskFactory::create_%s_task(url,%s config.retry_max(), callback); // Copy user's request to the new task's request using std::move() %s SeriesWork *series = series_of(server_task); series->set_context(server_task->get_resp()); series->push_back(client_task); fprintf(stderr, "proxy get request from client: "); print_peer_address(server_task); } int main() { init(); WF%sServer proxy_server(process); if (proxy_server.start(config.server_port()) == 0) { fprintf(stderr, "[%s]-[%s] proxy started, port %%u\n", config.server_port()); wait_group.wait(); proxy_server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tools/templates/proxy/proxy_main_proto.cc000066400000000000000000000042171525217424400244100ustar00rootroot00000000000000#include #include #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "config/config.h" #include "%s.srpc.h" using namespace srpc; static srpc::RPCConfig config; static WFFacilities::WaitGroup wait_group(1); static void sig_handler(int signo) { wait_group.done(); } static void init() { if (config.load("./proxy.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } class ProxyServiceImpl : public %s::Service { public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *context) override { fprintf(stderr, "%s proxy get request from client. ip : %%s\n%%s\n", context->get_remote_ip().c_str(), request->DebugString().c_str()); // 5. process() : get request from client and send to remote server auto *task = this->client.create_Echo_task([response](EchoResponse *resp, RPCContext *ctx) { // 6. callback() : fill remote server response to client if (ctx->success()) *response = std::move(*resp); }); task->serialize_input(request); context->get_series()->push_back(task); } public: ProxyServiceImpl(RPCClientParams *params) : client(params) { } private: %s::%sClient client; }; int main() { // 1. load config init(); // 2. make client for remote server RPCClientParams client_params = RPC_CLIENT_PARAMS_DEFAULT; client_params.host = config.client_host(); client_params.port = config.client_port(); // 3. start proxy server %sServer server; ProxyServiceImpl impl(&client_params); server.add_service(&impl); config.load_filter(server); if (server.start(config.server_port()) == 0) { // 4. main thread success and wait fprintf(stderr, "%s [%s]-[%s] proxy started, port %%u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tools/templates/rpc/000077500000000000000000000000001525217424400200705ustar00rootroot00000000000000sogou-srpc-ac65014/tools/templates/rpc/CMakeLists.txt000066400000000000000000000053601525217424400226340ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) project(%s LANGUAGES CXX) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_BUILD_TYPE RelWithDebInfo) set(Protobuf_ERROR_MSG "ERROR: Failed to find protobuf. Some suggestions for installation.") set(Protobuf_ERROR_MSG_LINUX "${Protobuf_ERROR_MSG} For Debian / Ubuntu ...: sudo apt-get update sudo apt-get install -y libprotobuf-dev protobuf-compiler For RedHat / Fedora / CentOS ...: sudo yum makecache sudo yum install -y protobuf-devel protobuf-compiler") set(Protobuf_ERROR_MSG_MACOS "${Protobuf_ERROR_MSG} For MacOS : sudo brew update sudo brew install protobuf protobuf-c") # Find all the dependencies find_package(OpenSSL REQUIRED) set(Workflow_DIR "%s") find_package(Workflow REQUIRED CONFIG HINTS ${Workflow_DIR}) set(Srpc_DIR "%s") find_package(srpc REQUIRED CONFIG HINTS ${Srpc_DIR}) find_package(Protobuf) if ("x${Protobuf_DIR}" STREQUAL "xProtobuf_DIR-NOTFOUND") if (APPLE) message (FATAL_ERROR ${Protobuf_ERROR_MSG_MACOS}) else () message (FATAL_ERROR ${Protobuf_ERROR_MSG_LINUX}) endif () endif () get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY) if (NOT EXISTS "${Srpc_DIR}/third_party/lz4/lib/lz4.h") set(LZ4_LIB lz4) endif () if (NOT EXISTS "${Srpc_DIR}/third_party/snappy/cmake") set(SNAPPY_LIB snappy) endif () find_package(ZLIB REQUIRED) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) # Generate idl code: xx.srpc.h xx.pb.h xx.pb.cc xx.thrift.h set(IDL_FILE %s) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/srpc_generator) %s add_custom_target(SRPC_GEN ALL COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/${IDL_FILE} ${PROJECT_SOURCE_DIR} -s COMMENT "srpc generator..." ) # Prefer to static link first set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES}) find_library(Workflow_LIB workflow HINTS ${Workflow_DIR}/_lib) find_library(Srpc_LIB srpc HINTS ${Srpc_DIR}/_lib) # Set all the libraries here set(LIB ${Srpc_LIB} ${Workflow_LIB} pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ${SNAPPY_LIB} ${LZ4_LIB}) # Add all the common code here set(COMMON_CODE config/config.cc config/Json.cc ${PROTO_SRCS}) # Add header directories and library directories here include_directories(${OPENSSL_INCLUDE_DIR} ${Protobuf_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR} ${SRPC_INCLUDE_DIR} ${PROJECT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) link_directories(${OPENSSL_LINK_DIR} ${Protobuf_LIB_DIR} ${WORKFLOW_LIB_DIR} ${SRPC_LIB_DIR}) # Build executable outputs set(PROJECT_OUTPUT server client%s) foreach(output ${PROJECT_OUTPUT}) add_executable(${output} ${output}_main.cc ${COMMON_CODE}) target_link_libraries(${output} ${LIB}) endforeach() sogou-srpc-ac65014/tools/templates/rpc/client.conf000066400000000000000000000002301525217424400222100ustar00rootroot00000000000000{ "client": { "remote_host": "127.0.0.1", "remote_port": 1412, "is_ssl" : false, "retry_max": 1, "callee" : "rpc_client" } } sogou-srpc-ac65014/tools/templates/rpc/client_protobuf.cc000066400000000000000000000030451525217424400235770ustar00rootroot00000000000000#include #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "%s.srpc.h" #include "config/config.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void init() { if (config.load("./client.conf") == false) { perror("Load config failed"); exit(1); } } int main() { // 1. load config init(); // 2. start client RPCClientParams params = RPC_CLIENT_PARAMS_DEFAULT; params.host = config.client_host(); params.port = config.client_port(); %s %s::%sClient client(¶ms); config.load_filter(client); // 3. request with sync api EchoRequest req; EchoResponse resp; RPCSyncContext ctx; req.set_message("Hello, this is sync request!"); client.Echo(&req, &resp, &ctx); if (ctx.success) fprintf(stderr, "sync resp. %%s\n", resp.DebugString().c_str()); else fprintf(stderr, "sync status[%%d] error[%%d] errmsg:%%s\n", ctx.status_code, ctx.error, ctx.errmsg.c_str()); // 4. request with async api req.set_message("Hello, this is async request!"); client.Echo(&req, [](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) fprintf(stderr, "async resp. %%s\n", resp->DebugString().c_str()); else fprintf(stderr, "async status[%%d] error[%%d] errmsg:%%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } sogou-srpc-ac65014/tools/templates/rpc/client_thrift.cc000066400000000000000000000031311525217424400232330ustar00rootroot00000000000000#include #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "%s.srpc.h" #include "config/config.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void init() { if (config.load("./client.conf") == false) { perror("Load config failed"); exit(1); } } int main() { // 1. load config init(); // 2. start client RPCClientParams params = RPC_CLIENT_PARAMS_DEFAULT; params.host = config.client_host(); params.port = config.client_port(); %s %s::%sClient client(¶ms); config.load_filter(client); // 3. request with sync api EchoResult res; client.Echo(res, "Hello, this is sync request!"); if (client.thrift_last_sync_success()) fprintf(stderr, "sync resp. %%s\n", res.message.c_str()); else { const auto& sync_ctx = client.thrift_last_sync_ctx(); fprintf(stderr, "sync status[%%d] error[%%d] errmsg:%%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); } // 4. request with async api %s::EchoRequest req; req.message = "Hello, this is async request!"; client.Echo(&req, [](%s::EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) fprintf(stderr, "async resp. %%s\n", resp->result.message.c_str()); else fprintf(stderr, "async status[%%d] error[%%d] errmsg:%%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); wait_group.wait(); return 0; } sogou-srpc-ac65014/tools/templates/rpc/rpc.proto000066400000000000000000000003711525217424400217420ustar00rootroot00000000000000syntax="proto2"; message EchoRequest { required string message = 1; }; message EchoResponse { required string message = 1; optional int32 state = 2; optional int32 error = 3; }; service %s { rpc Echo(EchoRequest) returns (EchoResponse); }; sogou-srpc-ac65014/tools/templates/rpc/rpc.thrift000066400000000000000000000002251525217424400220750ustar00rootroot00000000000000struct EchoResult { 1:required string message; 2:optional i32 state; 3:optional i32 error; } service %s { EchoResult Echo(1:string message); } sogou-srpc-ac65014/tools/templates/rpc/server.conf000066400000000000000000000000511525217424400222410ustar00rootroot00000000000000{ "server": { "port": 1412 } } sogou-srpc-ac65014/tools/templates/rpc/server_protobuf.cc000066400000000000000000000023771525217424400236360ustar00rootroot00000000000000#include #include #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "config/config.h" #include "%s.srpc.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./server.conf") == false) { perror("Load config failed"); exit(1); } signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); } class ServiceImpl : public %s::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { %s// 4. delete the following codes and fill your logic fprintf(stderr, "get req. %%s\n", req->DebugString().c_str()); resp->set_message("Hi back"); } }; int main() { // 1. load config init(); // 2. start server %sServer server; ServiceImpl impl; server.add_service(&impl); config.load_filter(server); if (server.start(config.server_port()) == 0) { // 3. success and wait fprintf(stderr, "%s %s server started, port %%u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tools/templates/rpc/server_thrift.cc000066400000000000000000000022411525217424400232640ustar00rootroot00000000000000#include #include #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "config/config.h" #include "%s.srpc.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); static srpc::RPCConfig config; void sig_handler(int signo) { wait_group.done(); } void init() { if (config.load("./server.conf") == false) { perror("Load config failed"); exit(1); } } class ServiceImpl : public %s::Service { public: void Echo(EchoResult& _return, const std::string& message) override { %s// 4. delete the following codes and fill your logic fprintf(stderr, "get req. %%s\n", message.c_str()); _return.message = "Hi back."; } }; int main() { // 1. load config init(); // 2. start server %sServer server; ServiceImpl impl; server.add_service(&impl); config.load_filter(server); if (server.start(config.server_port()) == 0) { // 3. success and wait printf("%s %s server started, port %%u\n", config.server_port()); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tutorial/000077500000000000000000000000001525217424400160115ustar00rootroot00000000000000sogou-srpc-ac65014/tutorial/CMakeLists.txt000066400000000000000000000161501525217424400205540ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.6) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "build type") project(srpc_tutorial LANGUAGES C CXX ) if (WIN32) option(SRPC_TUTORIAL_BUILD_STATIC_RUNTIME "Use static runtime" ON) endif () set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}) if (NOT "$ENV{LIBRARY_PATH}" STREQUAL "") string(REPLACE ":" ";" LIBRARY_PATH $ENV{LIBRARY_PATH}) set(CMAKE_SYSTEM_LIBRARY_PATH ${LIBRARY_PATH};${CMAKE_SYSTEM_LIBRARY_PATH}) endif () if (NOT "$ENV{CPLUS_INCLUDE_PATH}" STREQUAL "") string(REPLACE ":" ";" INCLUDE_PATH $ENV{CPLUS_INCLUDE_PATH}) set(CMAKE_SYSTEM_INCLUDE_PATH ${INCLUDE_PATH};${CMAKE_SYSTEM_INCLUDE_PATH}) endif () find_package(OpenSSL REQUIRED) find_package(srpc REQUIRED CONFIG HINTS ..) set(protobuf_MODULE_COMPATIBLE ON CACHE BOOL "") if (WIN32) find_package(Protobuf CONFIG REQUIRED) find_library(LZ4_LIBRARY NAMES lz4) if(LZ4_LIBRARY STREQUAL "LZ4_LIBRARY-NOTFOUND") find_package(lz4) if(lz4_FOUND) set(LZ4_LIBRARY ${lz4_LIBRARIES}) endif() endif() find_package(Snappy CONFIG REQUIRED) else () find_package(Protobuf REQUIRED) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/lz4/lib/lz4.h") set(LZ4_LIB lz4) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/snappy/cmake") set(SNAPPY_LIB snappy) endif () if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/workflow/workflow-config.cmake.in") find_package(Workflow REQUIRED CONFIG HINTS ../workflow) endif () find_package(ZLIB REQUIRED) include_directories( ${OPENSSL_INCLUDE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${Protobuf_INCLUDE_DIR} ${WORKFLOW_INCLUDE_DIR} ${SRPC_INCLUDE_DIR} ) if (WIN32) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/Debug/srpc_generator.exe) else () get_filename_component(Protobuf_LIB_DIR ${Protobuf_LIBRARY} DIRECTORY) link_directories(${SRPC_LIB_DIR} ${WORKFLOW_LIB_DIR} ${Protobuf_LIB_DIR}) set(SRPC_GEN_PROGRAM ${SRPC_BIN_DIR}/srpc_generator) endif () ### generating codes for echo_pb.proto set(PROTO_FILE "echo_pb.proto") protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${PROTO_FILE}) add_custom_target(ECHO_PB_CHANGED DEPENDS ${PROTO_SRCS} ${PROTO_HDRS}) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/${PROTO_FILE} ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak DEPENDS ${PROJECT_SOURCE_DIR}/${PROTO_FILE} COMMENT "srpc generator for ${PROTO_FILE}" ) add_custom_target(ECHO_PB_GENERATED DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak) add_dependencies(ECHO_PB_GENERATED ECHO_PB_CHANGED) ### generating codes for echo_thrift.thrift set(PROTO_FILE "echo_thrift.thrift") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak COMMAND ${SRPC_GEN_PROGRAM} ${PROJECT_SOURCE_DIR}/${PROTO_FILE} ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak DEPENDS ${PROJECT_SOURCE_DIR}/${PROTO_FILE} COMMENT "srpc generator for ${PROTO_FILE}" ) add_custom_target(THRIFT_GENERATED DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak) ### generating codes for helloworld.proto set(PROTO_FILE "helloworld.proto") protobuf_generate_cpp(HELLOWORLD_SRCS HELLOWORLD_HDRS ${PROTO_FILE}) add_custom_target(HELLOWORLD_CHANGED DEPENDS ${HELLOWORLD_SRCS} ${HELLOWORLD_HDRS}) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak COMMAND ${SRPC_GEN_PROGRAM} protobuf ${PROJECT_SOURCE_DIR}/${PROTO_FILE} ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak DEPENDS ${PROJECT_SOURCE_DIR}/${PROTO_FILE} COMMENT "srpc generator for ${PROTO_FILE}" ) add_custom_target(HELLOWORLD_GENERATED DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${PROTO_FILE}.bak) add_dependencies(HELLOWORLD_GENERATED HELLOWORLD_CHANGED) if (WIN32) if (SRPC_TUTORIAL_BUILD_STATIC_RUNTIME) set(CompilerFlags CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_MINSIZEREL ) foreach(CompilerFlag ${CompilerFlags}) string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") endforeach () endif () endif () if (WIN32) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP /wd4200") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4200 /Zc:__cplusplus /std:c++17") else () set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -fPIC -pipe -std=gnu90") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -fPIC -pipe -std=c++17 -fno-exceptions") endif () if (APPLE) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ${CMAKE_FIND_LIBRARY_SUFFIXES}) find_library(Workflow_LIB workflow HINTS ../workflow/_lib) find_library(Srpc_LIB srpc HINTS ../_lib) set(SRPC_LIB ${Srpc_LIB} ${Workflow_LIB} pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ) elseif (WIN32) set(SRPC_LIB srpc workflow ws2_32 wsock32 OpenSSL::SSL OpenSSL::Crypto protobuf::libprotobuf ZLIB::ZLIB Snappy::snappy ${LZ4_LIBRARY} ) else () set(SRPC_LIB srpc workflow pthread OpenSSL::SSL OpenSSL::Crypto protobuf z ${SNAPPY_LIB} ${LZ4_LIB} ) endif () set(TUTORIAL_PB_LIST tutorial-01-srpc_pb_server tutorial-02-srpc_pb_client tutorial-05-brpc_pb_server tutorial-06-brpc_pb_client tutorial-09-client_task tutorial-10-server_async tutorial-15-srpc_pb_proxy tutorial-16-server_with_metrics tutorial-19-custom_filter ) foreach(src ${TUTORIAL_PB_LIST}) string(REPLACE "-" ";" arr ${src}) list(GET arr -1 bin_name) add_executable(${bin_name} ${src}.cc ${PROTO_SRCS} ${PROTO_HDRS}) target_link_libraries(${bin_name} ${SRPC_LIB}) add_dependencies(${bin_name} ECHO_PB_GENERATED) endforeach() set(TUTORIAL_THRIFT_LIST tutorial-03-srpc_thrift_server tutorial-04-srpc_thrift_client tutorial-07-thrift_thrift_server tutorial-08-thrift_thrift_client ) foreach(src ${TUTORIAL_THRIFT_LIST}) string(REPLACE "-" ";" arr ${src}) list(GET arr -1 bin_name) add_executable(${bin_name} ${src}.cc) target_link_libraries(${bin_name} ${SRPC_LIB}) add_dependencies(${bin_name} THRIFT_GENERATED) endforeach() set(TUTORIAL_HELLOWORLD_LIST tutorial-11-trpc_pb_server tutorial-12-trpc_pb_client tutorial-13-trpc_http_server tutorial-14-trpc_http_client ) foreach(src ${TUTORIAL_HELLOWORLD_LIST}) string(REPLACE "-" ";" arr ${src}) list(GET arr -1 bin_name) add_executable(${bin_name} ${src}.cc ${HELLOWORLD_SRCS} ${HELLOWORLD_HDRS}) target_link_libraries(${bin_name} ${SRPC_LIB}) add_dependencies(${bin_name} HELLOWORLD_GENERATED) endforeach() if (NOT WIN32) set(TUTORIAL_HTTP_LIST tutorial-17-http_server tutorial-18-http_client ) foreach(src ${TUTORIAL_HTTP_LIST}) string(REPLACE "-" ";" arr ${src}) list(GET arr -1 bin_name) add_executable(${bin_name} ${src}.cc) target_link_libraries(${bin_name} ${SRPC_LIB}) add_dependencies(${bin_name} ECHO_PB_GENERATED) endforeach() endif () set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${CMAKE_CURRENT_BINARY_DIR}/echo_pb.proto.bak ${CMAKE_CURRENT_BINARY_DIR}/echo_thrift.proto.bak ${CMAKE_CURRENT_BINARY_DIR}/helloworld.proto.bak ) sogou-srpc-ac65014/tutorial/GNUmakefile000066400000000000000000000016161525217424400200670ustar00rootroot00000000000000ROOT_DIR := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) ALL_TARGETS := all clean MAKE_FILE := Makefile DEFAULT_BUILD_DIR := build.cmake BUILD_DIR := $(shell if [ -f $(MAKE_FILE) ]; then echo "."; else echo $(DEFAULT_BUILD_DIR); fi) CMAKE3 := $(shell if which cmake3>/dev/null ; then echo cmake3; else echo cmake; fi;) .PHONY: $(ALL_TARGETS) all: mkdir -p $(BUILD_DIR) ifeq ($(DEBUG),y) cd $(BUILD_DIR) && $(CMAKE3) -D CMAKE_BUILD_TYPE=Debug $(ROOT_DIR) else ifneq ("${Workflow_DIR}workflow", "workflow") cd $(BUILD_DIR) && $(CMAKE3) -DWorkflow_DIR:STRING=${Workflow_DIR} $(ROOT_DIR) else cd $(BUILD_DIR) && $(CMAKE3) $(ROOT_DIR) endif make -C $(BUILD_DIR) -f Makefile clean: ifeq ($(MAKE_FILE), $(wildcard $(MAKE_FILE))) -make -f Makefile clean else ifeq ($(DEFAULT_BUILD_DIR), $(wildcard $(DEFAULT_BUILD_DIR))) -make -C $(DEFAULT_BUILD_DIR) clean endif rm -rf $(DEFAULT_BUILD_DIR) sogou-srpc-ac65014/tutorial/echo_pb.proto000066400000000000000000000004311525217424400204730ustar00rootroot00000000000000syntax="proto2"; message EchoRequest { required string message = 1; required string name = 2; }; message EchoResponse { required string message = 1; optional int32 state = 2; optional int32 error = 3; }; service Example { rpc Echo(EchoRequest) returns (EchoResponse); }; sogou-srpc-ac65014/tutorial/echo_thrift.thrift000066400000000000000000000002511525217424400215270ustar00rootroot00000000000000struct EchoResult { 1:required string message; 2:optional i32 state; 3:optional i32 error; } service Example { EchoResult Echo(1:string message, 2:string name); } sogou-srpc-ac65014/tutorial/helloworld.proto000066400000000000000000000004101525217424400212440ustar00rootroot00000000000000syntax = "proto3"; package trpc.test.helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} rpc SayHi (HelloRequest) returns (HelloReply) {} } message HelloRequest { string msg = 1; } message HelloReply { string msg = 1; } sogou-srpc-ac65014/tutorial/tutorial-01-srpc_pb_server.cc000066400000000000000000000030711525217424400234160ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server; ExampleServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework SRPC Protobuf server is running on 1412\n" "Try ./srpc_pb_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-02-srpc_pb_client.cc000066400000000000000000000031161525217424400233670ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "echo_pb.srpc.h" #include "srpc/rpc_types.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); int main() { Example::SRPCClient client("127.0.0.1", 1412); //async EchoRequest req; req.set_message("Hello, srpc!"); req.set_name("1412"); client.Echo(&req, [](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); //sync EchoRequest sync_req; EchoResponse sync_resp; RPCSyncContext sync_ctx; sync_req.set_message("Hello, srpc!"); sync_req.set_name("Sync"); client.Echo(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-03-srpc_thrift_server.cc000066400000000000000000000031171525217424400243200ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_thrift.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); class ExampleServiceImpl : public Example::Service { public: void Echo(EchoResult& _return, const std::string& message, const std::string& name) override { _return.message = "Hi back, " + name; printf("Server Echo()\nreq_message:\n%s\nresp_message:\n%s\n", message.c_str(), _return.message.c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server; ExampleServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework SRPC protocol server with thrift IDL is running on 1412\n" "Try ./srpc_thrift_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-04-srpc_thrift_client.cc000066400000000000000000000036541525217424400242770ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "echo_thrift.srpc.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); int main() { Example::SRPCClient client("127.0.0.1", 1412); //sync EchoResult sync_res; client.Echo(sync_res, "Hello, srpc!", "1412"); if (client.thrift_last_sync_success()) printf("%s\n", sync_res.message.c_str()); else { const auto& sync_ctx = client.thrift_last_sync_ctx(); printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); } //async Example::EchoRequest req; req.message = "Hello, srpc!"; req.name = "1412"; client.Echo(&req, [](Example::EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->result.message.c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); //sync Example::EchoRequest sync_req; Example::EchoResponse sync_resp; RPCSyncContext sync_ctx; sync_req.message = "Hello, srpc!"; sync_req.name = "Sync"; client.Echo(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.result.message.c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-05-brpc_pb_server.cc000066400000000000000000000032151525217424400234010ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); char attachment[10] = "ATTACH"; class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back, " + req->name()); ctx->set_attachment_nocopy(attachment, strlen(attachment)); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); BRPCServer server; ExampleServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework BRPC Protobuf server is running on 1412\n" "Try ./brpc_pb_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-06-brpc_pb_client.cc000066400000000000000000000033301525217424400233500ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "echo_pb.srpc.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); int main() { Example::BRPCClient client("127.0.0.1", 1412); //async EchoRequest req; req.set_message("Hello, srpc!"); req.set_name("1412"); client.Echo(&req, [](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); const char *attachment; size_t len; while (ctx->get_attachment(&attachment, &len)) printf("get attachment [%.*s] len=%zu\n", (int)len, attachment, len); wait_group.done(); }); //sync EchoRequest sync_req; EchoResponse sync_resp; RPCSyncContext sync_ctx; sync_req.set_message("Hello, srpc!"); sync_req.set_name("Sync"); client.Echo(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-07-thrift_thrift_server.cc000066400000000000000000000030051525217424400246510ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_thrift.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); class ExampleServiceImpl : public Example::Service { public: void Echo(EchoResult& _return, const std::string& message, const std::string& name) override { _return.message = "Hi back, " + name; printf("Server Echo()\nreq_message:\n%s\nresp_message:\n%s\n", message.c_str(), _return.message.c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); ThriftServer server; ExampleServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework Thrift protocol server with thrift IDL is running on 1412\n" "Try ./thrift_thrift_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); return 0; } sogou-srpc-ac65014/tutorial/tutorial-08-thrift_thrift_client.cc000066400000000000000000000042521525217424400246270ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_thrift.srpc.h" using namespace srpc; int main() { Example::ThriftClient client("127.0.0.1", 1412); //sync EchoResult sync_res; client.Echo(sync_res, "Hello, srpc!", "1412"); if (client.thrift_last_sync_success()) printf("%s\n", sync_res.message.c_str()); else { const auto& sync_ctx = client.thrift_last_sync_ctx(); printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); } //send/recv client.send_Echo("Hello, srpc!", "1412"); //do anything you want client.recv_Echo(sync_res); if (client.thrift_last_sync_success()) printf("%s\n", sync_res.message.c_str()); else { const auto& sync_ctx = client.thrift_last_sync_ctx(); printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); } //async Example::EchoRequest req; req.message = "Hello, srpc!"; req.name = "1412"; client.Echo(&req, [](Example::EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->result.message.c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); //sync Example::EchoRequest sync_req; Example::EchoResponse sync_resp; RPCSyncContext sync_ctx; sync_req.message = "Hello, srpc!"; sync_req.name = "Sync"; client.Echo(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.result.message.c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); return 0; } sogou-srpc-ac65014/tutorial/tutorial-09-client_task.cc000066400000000000000000000036171525217424400227160ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; int main() { Example::SRPCClient client("127.0.0.1", 1412); EchoRequest req; req.set_message("Hello, srpc!"); req.set_name("1412"); auto *rpc_task = client.create_Echo_task([](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); auto *http_task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { std::string body; const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); body.assign((const char *)data, len); printf("%s\n\n", body.c_str()); } else printf("Http request fail\n\n"); }); rpc_task->serialize_input(&req); rpc_task->log({{"event", "info"}, {"message", "rpc client task log."}}); WFFacilities::WaitGroup wait_group(1); SeriesWork *series = Workflow::create_series_work(http_task, [&wait_group](const SeriesWork *) { wait_group.done(); }); series->push_back(rpc_task); series->start(); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-10-server_async.cc000066400000000000000000000042411525217424400231030ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { ctx->set_compress_type(RPCCompressGzip); ctx->log({{"event", "info"}, {"message", "rpc server echo() end."}}); auto *task = WFTaskFactory::create_http_task("https://www.sogou.com", 0, 0, [req, resp](WFHttpTask *task) { if (task->get_state() == WFT_STATE_SUCCESS) { const void *data; size_t len; task->get_resp()->get_parsed_body(&data, &len); resp->mutable_message()->assign((const char *)data, len); } else resp->set_message("Error: " + std::to_string(task->get_error())); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); }); ctx->get_series()->push_back(task); } }; static void sig_handler(int signo) { wait_group.done(); } int main(int argc, char *argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server; ExampleServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("Asynchronous server with HTTP requests is running on 1412\n" "Try ./client_task or ./srpc_pb_client to trigger this example.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-11-trpc_pb_server.cc000066400000000000000000000036751525217424400234320ustar00rootroot00000000000000/* Copyright (c) 2021 sogou, Inc. 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. */ #include #include "helloworld.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" using namespace srpc; using namespace trpc::test::helloworld; static WFFacilities::WaitGroup wait_group(1); class GreeterServiceImpl : public Greeter::Service { public: void SayHello(HelloRequest *req, HelloReply *resp, RPCContext *ctx) override { ctx->set_compress_type(RPCCompressGzip); resp->set_msg("This is SRPC framework TRPC protocol. Hello back."); printf("Server SayHello()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } void SayHi(HelloRequest *req, HelloReply *resp, RPCContext *ctx) override { resp->set_msg("This is SRPC framework TRPC protocol. Hi back."); printf("Server SayHi()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); TRPCServer server; GreeterServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework TRPC server is running on 1412\n" "Try ./trpc_pb_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-12-trpc_pb_client.cc000066400000000000000000000032241525217424400233710ustar00rootroot00000000000000/* Copyright (c) 2021 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "helloworld.srpc.h" #include "srpc/rpc_types.h" using namespace srpc; using namespace trpc::test::helloworld; static WFFacilities::WaitGroup wait_group(1); int main() { Greeter::TRPCClient client("127.0.0.1", 1412); //async HelloRequest req; req.set_msg("Hello, trpc server. This is srpc framework trpc client!"); client.SayHello(&req, [](HelloReply *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); wait_group.done(); }); //sync HelloRequest sync_req; HelloReply sync_resp; RPCSyncContext sync_ctx; sync_req.set_msg("Hi, trpc server. This is srpc framework trpc client!"); client.SayHi(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-13-trpc_http_server.cc000066400000000000000000000037071525217424400240060ustar00rootroot00000000000000/* Copyright (c) 2021 sogou, Inc. 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. */ #include #include "helloworld.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" using namespace srpc; using namespace trpc::test::helloworld; static WFFacilities::WaitGroup wait_group(1); class GreeterServiceImpl : public Greeter::Service { public: void SayHello(HelloRequest *req, HelloReply *resp, RPCContext *ctx) override { ctx->set_compress_type(RPCCompressGzip); resp->set_msg("This is SRPC framework TRPC protocol. Hello back."); printf("Server SayHello()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } void SayHi(HelloRequest *req, HelloReply *resp, RPCContext *ctx) override { resp->set_msg("This is SRPC framework TRPC protocol. Hi back."); printf("Server SayHi()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); TRPCHttpServer server; GreeterServiceImpl impl; server.add_service(&impl); if (server.start(1412) == 0) { printf("SRPC framework TRPCHttp server is running on 1412\n" "Try ./trpc_http_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-14-trpc_http_client.cc000066400000000000000000000032261525217424400237530ustar00rootroot00000000000000/* Copyright (c) 2021 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "helloworld.srpc.h" #include "srpc/rpc_types.h" using namespace srpc; using namespace trpc::test::helloworld; static WFFacilities::WaitGroup wait_group(1); int main() { Greeter::TRPCHttpClient client("127.0.0.1", 1412); //async HelloRequest req; req.set_msg("Hello, trpc-http server. This is srpc framework trpc-http client!"); client.SayHello(&req, [](HelloReply *resp, RPCContext *ctx) { if (ctx->success()) printf("%s\n", resp->DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }); //sync HelloRequest sync_req; HelloReply sync_resp; RPCSyncContext sync_ctx; sync_req.set_msg("Hi, trpc-http server. This is srpc framework trpc-http client!"); client.SayHi(&sync_req, &sync_resp, &sync_ctx); if (sync_ctx.success) printf("%s\n", sync_resp.DebugString().c_str()); else printf("status[%d] error[%d] errmsg:%s\n", sync_ctx.status_code, sync_ctx.error, sync_ctx.errmsg.c_str()); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-15-srpc_pb_proxy.cc000066400000000000000000000042041525217424400232750ustar00rootroot00000000000000/* Copyright (c) 2020 sogou, Inc. 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. */ #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); template class ExampleProxyServiceImpl : public Example::Service { public: ExampleProxyServiceImpl(CLIENT *client) { this->client = client; } private: CLIENT *client; public: void Echo(EchoRequest *request, EchoResponse *response, RPCContext *context) override { printf("Proxy Server Echo() get and transfer request:\n%s\n", request->DebugString().c_str()); auto *task = this->client->create_Echo_task([response](EchoResponse *resp, RPCContext *ctx) { printf("Proxy Server Echo() get and transfer response:\n%s\n", resp->DebugString().c_str()); if (ctx->success()) *response = std::move(*resp); }); task->serialize_input(request); context->get_series()->push_back(task); } }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server; Example::SRPCClient client("127.0.0.1", 1412); ExampleProxyServiceImpl impl(&client); server.add_service(&impl); if (server.start(61412) == 0) { printf("Proxy server serving on 61412 and redirect to backend server 127.0.0.1:1412\n" "Try start ./srpc_pb_server as backend and send requests to 61412.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-16-server_with_metrics.cc000066400000000000000000000044461525217424400245040ustar00rootroot00000000000000/* Copyright (c) 2022 sogou, Inc. 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. */ #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "srpc/rpc_metrics_filter.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); printf("Server Echo()\nget_req:\n%s\nset_resp:\n%s\n", req->DebugString().c_str(), resp->DebugString().c_str()); this->filter->histogram("echo_request_size")->observe(req->ByteSizeLong()); for (size_t i = 1; i <= 10; i++) this->filter->summary("echo_test_quantiles")->observe(0.01 * i); } void set_filter(RPCMetricsPull *filter) { this->filter = filter; } private: RPCMetricsPull *filter; }; static void sig_handler(int signo) { wait_group.done(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); SRPCServer server; ExampleServiceImpl impl; RPCMetricsPull filter; filter.init(8080); /* export port for prometheus */ filter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100}); filter.create_summary("echo_test_quantiles", "Test quantile", {{0.5, 0.05}, {0.9, 0.01}}); impl.set_filter(&filter); server.add_filter(&filter); server.add_service(&impl); if (server.start(1412) == 0) { printf("Server with metrics for Promethes(port:8080) is running on 1412\n" "Try ./srpc_pb_client to send requests.\n" "Try ' curl http://localhost:8080/metrics ' to get prometheus data.\n"); wait_group.wait(); server.stop(); } else perror("server start"); filter.deinit(); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-17-http_server.cc000066400000000000000000000036001525217424400227520ustar00rootroot00000000000000/* Copyright (c) 2023 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "srpc/http_server.h" #include "srpc/rpc_types.h" #include "srpc/rpc_metrics_filter.h" #include "srpc/rpc_trace_filter.h" static WFFacilities::WaitGroup wait_group(1); srpc::RPCMetricsPull exporter; srpc::RPCTraceDefault trace_log; //srpc::RPCTraceOpenTelemetry otel("http://127.0.0.1:4318"); static void sig_handler(int signo) { wait_group.done(); } void process(WFHttpTask *task) { fprintf(stderr, "http server get request_uri: %s\n", task->get_req()->get_request_uri()); task->get_resp()->append_output_body("Hello from server!"); } int main() { signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); srpc::HttpServer server(process); exporter.init(8080); /* export port for prometheus */ exporter.create_histogram("echo_request_size", "Echo request size", {1, 10, 100}); exporter.create_summary("echo_test_quantiles", "Test quantile", {{0.5, 0.05}, {0.9, 0.01}}); server.add_filter(&trace_log); // server.add_filter(&otel); server.add_filter(&exporter); if (server.start(1412) == 0) { printf("HTTP protocol server is running on 1412\n" "Try ./http_client to send requests.\n\n"); wait_group.wait(); server.stop(); } else perror("server start"); exporter.deinit(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-18-http_client.cc000066400000000000000000000032011525217424400227200ustar00rootroot00000000000000/* Copyright (c) 2023 sogou, Inc. 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. */ #include #include "workflow/WFFacilities.h" #include "srpc/http_client.h" #include "srpc/rpc_types.h" #include "srpc/rpc_metrics_filter.h" #include "srpc/rpc_trace_filter.h" using namespace srpc; #define REDIRECT_MAX 5 #define RETRY_MAX 2 srpc::RPCTraceDefault trace_log; static WFFacilities::WaitGroup wait_group(1); void callback(WFHttpTask *task) { int state = task->get_state(); int error = task->get_error(); fprintf(stderr, "callback. state = %d error = %d\n", state, error); if (state == WFT_STATE_SUCCESS) // print server response body { const void *body; size_t body_len; task->get_resp()->get_parsed_body(&body, &body_len); fwrite(body, 1, body_len, stdout); fflush(stdout); fprintf(stderr, "\nfinish print body. body_len = %zu\n", body_len); } wait_group.done(); } int main() { srpc::HttpClient client; client.add_filter(&trace_log); WFHttpTask *task = client.create_http_task("http://127.0.0.1:1412", REDIRECT_MAX, RETRY_MAX, callback); task->start(); wait_group.wait(); return 0; } sogou-srpc-ac65014/tutorial/tutorial-19-custom_filter.cc000066400000000000000000000062431525217424400232740ustar00rootroot00000000000000/* Copyright (c) 2024 sogou, Inc. 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. */ #include #include "echo_pb.srpc.h" #include "workflow/WFFacilities.h" #include "srpc/rpc_types.h" #include "srpc/rpc_module.h" #include "srpc/rpc_basic.h" using namespace srpc; static WFFacilities::WaitGroup wait_group(1); // Filter is available for both server and client. // Please choose the function to implement the logic at the corresponding time. class MyFilter : public RPCFilter { public: MyFilter() : RPCFilter(RPCModuleTypeCustom) { } bool server_begin(SubTask *task, RPCModuleData& data) override { auto iter = data.find("my_auth_key"); if (iter != data.end() && iter->second.compare("my_auth_value") == 0) { fprintf(stderr, "[FILTER] auth success : %s\n", iter->second.c_str()); return true; } fprintf(stderr, "[FILTER] auth failed : %s\n", iter == data.end() ? "No auth" : iter->second.c_str()); return false; } }; class ExampleServiceImpl : public Example::Service { public: void Echo(EchoRequest *req, EchoResponse *resp, RPCContext *ctx) override { resp->set_message("Hi back"); fprintf(stderr, "[SERVER] Echo() get req: %s\n", req->message().c_str()); } }; static void sig_handler(int signo) { wait_group.done(); } void send_client_task() { Example::SRPCClient client("127.0.0.1", 1412); auto callback = [](EchoResponse *resp, RPCContext *ctx) { if (ctx->success()) fprintf(stderr, "[CLIENT] callback success\n"); else fprintf(stderr, "[CLIENT] callback status[%d] error[%d] errmsg : %s\n", ctx->get_status_code(), ctx->get_error(), ctx->get_errmsg()); }; EchoRequest req; req.set_name("Tutorial 19"); // send one task to test the success case auto *task = client.create_Echo_task(callback); req.set_message("For success case"); task->serialize_input(&req); task->add_baggage("my_auth_key", "my_auth_value"); task->start(); // send another task to test the failure case task = client.create_Echo_task(callback); req.set_message("For failure case"); task->serialize_input(&req); task->add_baggage("my_auth_key", "randomxxx"); task->start(); } int main() { GOOGLE_PROTOBUF_VERIFY_VERSION; signal(SIGINT, sig_handler); signal(SIGTERM, sig_handler); // 1. prepare server SRPCServer server; ExampleServiceImpl impl; server.add_service(&impl); // 2. add filter into server MyFilter my; server.add_filter(&my); // 3. run server if (server.start(1412) == 0) { fprintf(stderr, "[SERVER] Server with filter is running on 1412\n"); // 4. send client task to test send_client_task(); wait_group.wait(); server.stop(); } else perror("[SERVER] server start"); google::protobuf::ShutdownProtobufLibrary(); return 0; } sogou-srpc-ac65014/workflow/000077500000000000000000000000001525217424400160205ustar00rootroot00000000000000