From 8e7a1619064ecebfbb215751c05848da7b202450 Mon Sep 17 00:00:00 2001 From: Mike Schore Date: Fri, 7 Jun 2019 09:18:02 -0700 Subject: [PATCH] Initial release (#48) Co-authored-by: Keith Smiley Co-authored-by: Jose Ulises Nino Rivera Co-authored-by: Tony Allen Co-authored-by: Michael Rebello Co-authored-by: Alan Chiu Co-authored-by: Matt Klein Signed-off-by: Mike Schore Signed-off-by: JP Simard --- mobile/.bazelrc | 19 +++ mobile/.clang-format | 1 + mobile/.gitignore | 13 +- mobile/BUILD | 53 ++++++++ mobile/README.md | 48 ++++++- mobile/WORKSPACE | 88 +++++++++++++ mobile/bazel/BUILD | 1 + mobile/bazel/aar_with_jni.bzl | 42 +++++++ mobile/bazel/ranlib.patch | 10 ++ mobile/dist/BUILD | 23 ++++ .../docs/development/binary_size_analysis.md | 84 +++++++++++++ mobile/envoy | 2 +- mobile/envoy_build_config/BUILD | 1 + mobile/envoy_build_config/WORKSPACE | 0 .../extensions_build_config.bzl | 5 + mobile/examples/common/config.yaml | 33 +++++ .../java/hello_world/AndroidManifest.xml | 23 ++++ mobile/examples/java/hello_world/BUILD | 19 +++ .../java/hello_world/MainActivity.java | 117 ++++++++++++++++++ .../examples/java/hello_world/Response.java | 13 ++ .../ResponseRecyclerViewAdapter.java | 37 ++++++ .../java/hello_world/ResponseViewHolder.java | 22 ++++ .../hello_world/res/layout/activity_main.xml | 15 +++ .../java/hello_world/res/layout/item.xml | 22 ++++ .../java/hello_world/res/raw/config.yaml | 1 + .../java/hello_world/res/values/colors.xml | 4 + .../java/hello_world/res/values/strings.xml | 5 + .../java/hello_world/res/values/styles.xml | 6 + .../objective-c/hello_world/AppDelegate.h | 8 ++ .../objective-c/hello_world/AppDelegate.mm | 38 ++++++ mobile/examples/objective-c/hello_world/BUILD | 25 ++++ .../objective-c/hello_world/Info.plist | 36 ++++++ .../objective-c/hello_world/README.md | 5 + .../objective-c/hello_world/ViewController.h | 4 + .../objective-c/hello_world/ViewController.mm | 113 +++++++++++++++++ .../objective-c/hello_world/config.yaml | 1 + .../examples/objective-c/hello_world/main.mm | 7 ++ .../swift/hello_world/AppDelegate.swift | 42 +++++++ mobile/examples/swift/hello_world/BUILD | 24 ++++ mobile/examples/swift/hello_world/Info.plist | 36 ++++++ mobile/examples/swift/hello_world/README.md | 5 + .../swift/hello_world/ResponseValue.swift | 4 + .../swift/hello_world/ViewController.swift | 82 ++++++++++++ mobile/examples/swift/hello_world/config.yaml | 1 + mobile/library/EnvoyManifest.xml | 8 ++ mobile/library/common/BUILD | 24 ++++ mobile/library/common/jni_interface.cc | 38 ++++++ mobile/library/common/main_interface.cc | 50 ++++++++ mobile/library/common/main_interface.h | 12 ++ .../java/io/envoyproxy/envoymobile/Envoy.java | 31 +++++ mobile/tools/check_format.sh | 15 ++- 51 files changed, 1311 insertions(+), 5 deletions(-) create mode 100644 mobile/.bazelrc create mode 120000 mobile/.clang-format create mode 100644 mobile/BUILD create mode 100644 mobile/WORKSPACE create mode 100644 mobile/bazel/BUILD create mode 100644 mobile/bazel/aar_with_jni.bzl create mode 100644 mobile/bazel/ranlib.patch create mode 100644 mobile/dist/BUILD create mode 100644 mobile/docs/development/binary_size_analysis.md create mode 100644 mobile/envoy_build_config/BUILD create mode 100644 mobile/envoy_build_config/WORKSPACE create mode 100644 mobile/envoy_build_config/extensions_build_config.bzl create mode 100644 mobile/examples/common/config.yaml create mode 100644 mobile/examples/java/hello_world/AndroidManifest.xml create mode 100644 mobile/examples/java/hello_world/BUILD create mode 100644 mobile/examples/java/hello_world/MainActivity.java create mode 100644 mobile/examples/java/hello_world/Response.java create mode 100644 mobile/examples/java/hello_world/ResponseRecyclerViewAdapter.java create mode 100644 mobile/examples/java/hello_world/ResponseViewHolder.java create mode 100644 mobile/examples/java/hello_world/res/layout/activity_main.xml create mode 100644 mobile/examples/java/hello_world/res/layout/item.xml create mode 120000 mobile/examples/java/hello_world/res/raw/config.yaml create mode 100644 mobile/examples/java/hello_world/res/values/colors.xml create mode 100644 mobile/examples/java/hello_world/res/values/strings.xml create mode 100644 mobile/examples/java/hello_world/res/values/styles.xml create mode 100644 mobile/examples/objective-c/hello_world/AppDelegate.h create mode 100644 mobile/examples/objective-c/hello_world/AppDelegate.mm create mode 100644 mobile/examples/objective-c/hello_world/BUILD create mode 100644 mobile/examples/objective-c/hello_world/Info.plist create mode 100644 mobile/examples/objective-c/hello_world/README.md create mode 100644 mobile/examples/objective-c/hello_world/ViewController.h create mode 100644 mobile/examples/objective-c/hello_world/ViewController.mm create mode 120000 mobile/examples/objective-c/hello_world/config.yaml create mode 100644 mobile/examples/objective-c/hello_world/main.mm create mode 100644 mobile/examples/swift/hello_world/AppDelegate.swift create mode 100644 mobile/examples/swift/hello_world/BUILD create mode 100644 mobile/examples/swift/hello_world/Info.plist create mode 100644 mobile/examples/swift/hello_world/README.md create mode 100644 mobile/examples/swift/hello_world/ResponseValue.swift create mode 100644 mobile/examples/swift/hello_world/ViewController.swift create mode 120000 mobile/examples/swift/hello_world/config.yaml create mode 100644 mobile/library/EnvoyManifest.xml create mode 100644 mobile/library/common/BUILD create mode 100644 mobile/library/common/jni_interface.cc create mode 100644 mobile/library/common/main_interface.cc create mode 100644 mobile/library/common/main_interface.h create mode 100644 mobile/library/java/io/envoyproxy/envoymobile/Envoy.java diff --git a/mobile/.bazelrc b/mobile/.bazelrc new file mode 100644 index 000000000000..6f8033b79256 --- /dev/null +++ b/mobile/.bazelrc @@ -0,0 +1,19 @@ +startup --host_jvm_args=-Xmx512m + +build \ + --define=google_grpc=disabled \ + --define=hot_restart=disabled \ + --define=signal_trace=disabled \ + --define=tcmalloc=disabled \ + --ios_minimum_os=10.0 \ + --ios_simulator_device="iPhone X" \ + --ios_simulator_version=12.2 \ + --spawn_strategy=standalone \ + --verbose_failures \ + --workspace_status_command=envoy/bazel/get_workspace_status \ + --xcode_version=10.2.1 \ + --incompatible_bzl_disallow_load_after_statement=false + +build:ios --define=manual_stamp=manual_stamp + +build:android --fat_apk_cpu=x86,x86_64,armeabi-v7a,arm64-v8a diff --git a/mobile/.clang-format b/mobile/.clang-format new file mode 120000 index 000000000000..30fec7da9d92 --- /dev/null +++ b/mobile/.clang-format @@ -0,0 +1 @@ +envoy/.clang-format \ No newline at end of file diff --git a/mobile/.gitignore b/mobile/.gitignore index c71c2a94c60c..f4608f9341c8 100644 --- a/mobile/.gitignore +++ b/mobile/.gitignore @@ -1,2 +1,11 @@ -build_docs/ -generated/ +/bazel-* +/build_* +/dist/envoy.aar +/dist/Envoy.framework +.DS_Store +/generated +.idea +.ijwb +*.pyc +tags +.vscode diff --git a/mobile/BUILD b/mobile/BUILD new file mode 100644 index 000000000000..5b76c57b004b --- /dev/null +++ b/mobile/BUILD @@ -0,0 +1,53 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_framework", "ios_static_framework") + +envoy_package() + +load("//bazel:aar_with_jni.bzl", "aar_with_jni") + +ios_static_framework( + name = "ios_framework", + hdrs = ["//library/common:main_interface.h"], + bundle_name = "Envoy", + minimum_os_version = "10.0", + visibility = ["//visibility:public"], + deps = ["//library/common:envoy_main_interface_lib"], +) + +genrule( + name = "ios_dist", + srcs = ["//:ios_framework"], + outs = ["ios_out"], + cmd = """ +unzip -o $< -d dist/ +touch $@ +""", +) + +aar_with_jni( + name = "android_aar", + android_library = "android_lib", + archive_name = "envoy", + visibility = ["//visibility:public"], +) + +android_library( + name = "android_lib", + srcs = ["library/java/io/envoyproxy/envoymobile/Envoy.java"], + custom_package = "io.envoyproxy.envoymobile", + manifest = "library/EnvoyManifest.xml", + deps = ["//library/common:envoy_jni_interface_lib"], +) + +genrule( + name = "android_dist", + srcs = ["//:android_aar"], + outs = ["android_out"], + cmd = """ +cp $< dist/envoy.aar +chmod 755 dist/envoy.aar +touch $@ +""", +) diff --git a/mobile/README.md b/mobile/README.md index f46edd62898b..d17b986b468d 100644 --- a/mobile/README.md +++ b/mobile/README.md @@ -1,4 +1,4 @@ -# Envoy Mobile + # Envoy Mobile Mobile client networking libraries based on the [Envoy](https://www.envoyproxy.io/) project. @@ -31,3 +31,49 @@ copyrighted by the *Envoy proxy authors* and the [Cloud Native Computing Foundation](https://cncf.io). The Envoy name is used with permission from the CNCF under the assumption that if this project finds traction it will be donated to the Envoy proxy project. + +## Building + +### Requirements + +- Xcode 10.2.1 +- Bazel 0.25.3 (can't use 0.26.0 because of [this issue](https://github.com/bazelbuild/tulsi/issues/86)) + +### iOS + +#### Build the iOS static framework: + +``` +bazel build ios_dist --config=ios +``` + +This will yield an `Envoy.framework` in the [`dist` directory](./dist). + +#### Run the Swift app: + +``` +bazel run //examples/swift/hello_world:app --config=ios +``` + +#### Run the Objective-C app: + +``` +bazel run //examples/objective-c/hello_world:app --config=ios +``` + +### Android + +#### Build the Android AAR: + +``` +bazel build android_dist --config=android +``` + +#### Run the Java app: + +``` +bazel mobile-install //examples/java/hello_world:hello_envoy --fat_apk_cpu=x86 +``` + +If you have issues getting Envoy to compile locally, see the +[Envoy docs on building with Bazel](https://github.com/envoyproxy/envoy/tree/master/bazel). diff --git a/mobile/WORKSPACE b/mobile/WORKSPACE new file mode 100644 index 000000000000..56b2418fd8d9 --- /dev/null +++ b/mobile/WORKSPACE @@ -0,0 +1,88 @@ +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# TODO remove once https://github.com/bazelbuild/rules_foreign_cc/pull/253 is resolved +# NOTE: this version should be kept up to date with https://github.com/lyft/envoy-edge-fork/blob/3573b07af1ab5c4cf687ced0f80e2ccc0a0b7ec2/bazel/repository_locations.bzl#L225-L230 until this is removed +http_archive( + name = "rules_foreign_cc", + patches = ["//bazel:ranlib.patch"], + sha256 = "e1b67e1fda647c7713baac11752573bfd4c2d45ef09afb4d4de9eb9bd4e5ac76", + strip_prefix = "rules_foreign_cc-8648b0446092ef2a34d45b02c8dc4c35c3a8df79", + urls = ["https://github.com/bazelbuild/rules_foreign_cc/archive/8648b0446092ef2a34d45b02c8dc4c35c3a8df79.tar.gz"], +) + +load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependencies") + +local_repository( + name = "envoy", + path = "envoy", +) + +local_repository( + name = "envoy_build_config", + path = "envoy_build_config", +) + +git_repository( + name = "build_bazel_rules_apple", + commit = "2f20f88d85c0fe217cf9a9eadfb8015b3a384dea", + remote = "https://github.com/bazelbuild/rules_apple.git", +) + +load("@envoy//bazel:api_repositories.bzl", "envoy_api_dependencies") + +envoy_api_dependencies() + +load("@envoy//bazel:repositories.bzl", "GO_VERSION", "envoy_dependencies") +load("@envoy//bazel:cc_configure.bzl", "cc_configure") + +envoy_dependencies() + +load("@rules_foreign_cc//:workspace_definitions.bzl", "rules_foreign_cc_dependencies") + +rules_foreign_cc_dependencies() + +cc_configure() + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +go_register_toolchains(go_version = GO_VERSION) + +git_repository( + name = "build_bazel_apple_support", + commit = "dabf718975760b4d45bd7db3e18bc12e7b4ef485", + remote = "https://github.com/bazelbuild/apple_support.git", + shallow_since = "1551297696 -0500", +) + +git_repository( + name = "build_bazel_rules_swift", + commit = "e517571f92fe7739635c6e25b2be1f4f185fce33", + remote = "https://github.com/bazelbuild/rules_swift.git", + shallow_since = "1551797865 -0800", +) + +load("@build_bazel_apple_support//lib:repositories.bzl", "apple_support_dependencies") + +apple_support_dependencies() + +load("@build_bazel_rules_apple//apple:repositories.bzl", "apple_rules_dependencies") + +apple_rules_dependencies(ignore_version_differences = True) + +load("@build_bazel_rules_swift//swift:repositories.bzl", "swift_rules_dependencies") + +swift_rules_dependencies() + +android_sdk_repository(name = "androidsdk") + +android_ndk_repository(name = "androidndk") + +git_repository( + name = "rules_jvm_external", + commit = "fc5bd21820581f342a4119a89bfdf36e79c6c549", + remote = "https://github.com/bazelbuild/rules_jvm_external.git", + shallow_since = "1552938175 -0400", +) diff --git a/mobile/bazel/BUILD b/mobile/bazel/BUILD new file mode 100644 index 000000000000..779d1695d3b7 --- /dev/null +++ b/mobile/bazel/BUILD @@ -0,0 +1 @@ +licenses(["notice"]) # Apache 2 diff --git a/mobile/bazel/aar_with_jni.bzl b/mobile/bazel/aar_with_jni.bzl new file mode 100644 index 000000000000..9932cfb52251 --- /dev/null +++ b/mobile/bazel/aar_with_jni.bzl @@ -0,0 +1,42 @@ +# Based on https://github.com/aj-michael/aar_with_jni + +def aar_with_jni(name, android_library, archive_name = "", visibility = None): + if not archive_name: + archive_name = name + + native.genrule( + name = archive_name + "_binary_manifest_generator", + outs = [archive_name + "_generated_AndroidManifest.xml"], + cmd = """ +cat > $(OUTS) < + + +EOF +""", + ) + + native.android_binary( + name = archive_name + "_jni", + manifest = archive_name + "_generated_AndroidManifest.xml", + custom_package = "does.not.matter", + deps = [android_library], + ) + + native.genrule( + name = name, + srcs = [android_library + ".aar", archive_name + "_jni_unsigned.apk"], + outs = [archive_name + ".aar"], + cmd = """ +cp $(location {}.aar) $(location :{}.aar) +chmod +w $(location :{}.aar) +origdir=$$PWD +cd $$(mktemp -d) +unzip $$origdir/$(location :{}_jni_unsigned.apk) "lib/*" +cp -r lib jni +zip -r $$origdir/$(location :{}.aar) jni/*/*.so +""".format(android_library, archive_name, archive_name, archive_name, archive_name), + visibility = visibility, + ) diff --git a/mobile/bazel/ranlib.patch b/mobile/bazel/ranlib.patch new file mode 100644 index 000000000000..6fd6aeb9f788 --- /dev/null +++ b/mobile/bazel/ranlib.patch @@ -0,0 +1,10 @@ +--- tools/build_defs/cmake_script.bzl ++++ tools/build_defs/cmake_script.bzl +@@ -91,6 +91,7 @@ _CMAKE_ENV_VARS_FOR_CROSSTOOL = { + _CMAKE_CACHE_ENTRIES_CROSSTOOL = { + "CMAKE_SYSTEM_NAME": struct(value = "CMAKE_SYSTEM_NAME", replace = True), + "CMAKE_AR": struct(value = "CMAKE_AR", replace = True), ++ "CMAKE_RANLIB": struct(value = "CMAKE_RANLIB", replace = True), + "CMAKE_CXX_LINK_EXECUTABLE": struct(value = "CMAKE_CXX_LINK_EXECUTABLE", replace = True), + "CMAKE_C_FLAGS": struct(value = "CMAKE_C_FLAGS_INIT", replace = False), + "CMAKE_CXX_FLAGS": struct(value = "CMAKE_CXX_FLAGS_INIT", replace = False), diff --git a/mobile/dist/BUILD b/mobile/dist/BUILD new file mode 100644 index 000000000000..b3f4b487cc82 --- /dev/null +++ b/mobile/dist/BUILD @@ -0,0 +1,23 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@build_bazel_rules_apple//apple:apple.bzl", "apple_static_framework_import") + +envoy_package() + +# NOTE: You must first build the top-level targets //:ios_dist and //:android_dist to use the +# artifacts referenced here. + +aar_import( + name = "envoy_mobile_android", + aar = "envoy.aar", +) + +apple_static_framework_import( + name = "envoy_mobile_ios", + framework_imports = glob(["Envoy.framework/**"]), + sdk_dylibs = [ + "resolv.9", + "c++", + ], +) diff --git a/mobile/docs/development/binary_size_analysis.md b/mobile/docs/development/binary_size_analysis.md new file mode 100644 index 000000000000..6e471221837a --- /dev/null +++ b/mobile/docs/development/binary_size_analysis.md @@ -0,0 +1,84 @@ +Analysis of Binary Size +----------------------- + +### Creating a test binary + +Investigations into the top file size contributions of the Envoy library have +been performed via a simple test program that imports the `main_interface.h` +file: + +``` +#include "main_interface.h" + +using namespace std; + +int main() { + return 0; +} +``` + +One will then need to create the binary via bazel build using a patch similar +to: +``` +diff --git a/library/common/BUILD b/library/common/BUILD +index cbf681c..e2162af 100644 +--- a/library/common/BUILD ++++ b/library/common/BUILD +@@ -1,6 +1,6 @@ + licenses(["notice"]) # Apache 2 + +-load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package") ++load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package", "envoy_cc_binary") + + envoy_package() + +@@ -11,3 +11,10 @@ envoy_cc_library( + repository = "@envoy", + deps = ["@envoy//source/exe:envoy_main_common_lib"] + ) ++ ++envoy_cc_binary( ++ name = "test_binary", ++ srcs = ["test_binary.cc"], ++ repository = "@envoy", ++ deps = [":envoy_main_interface_lib"] ++) +``` + +When building the binary, using the following flags in the .bazelrc would yield a binary with +the correct compiler optimizations for the purposes of the investigation: + +``` +build:tinybuild --copt="-Os" +build:tinybuild --define google_grpc=disabled +build:tinybuild --define signal_trace=disabled +build:tinybuild --define tcmalloc=disabled +build:tinybuild --define disable_hot_restart=enabled +``` + +One can build both an unstripped (for the debug symbols) and a stripped binary (for analysis) via: + +``` +bazel build //library/common:test_binary --config=tinybuild --copt="-g" + +- or - + +bazel build //library/common:test_binary.stripped --config=tinybuild --copt="-g" +``` + +### Performing analysis + +[Bloaty](https://github.com/google/bloaty) has been more useful than `objdump` +when performing the investigation. The main command used to generate reports is +the following from the root of the build directory: + +``` +bloaty -w \ + -s file \ + -n 0 \ + --debug-file=bazel-bin/library/common/test_binary \ + -d compileunits \ + bazel-bin/library/common/test_binary.stripped +``` + +For a more detailed breakdown, one can substitute `-d compileunits,inlined`. diff --git a/mobile/envoy b/mobile/envoy index 0e109cb3ba3b..71f1abaaef67 160000 --- a/mobile/envoy +++ b/mobile/envoy @@ -1 +1 @@ -Subproject commit 0e109cb3ba3be0823bdb696eacb02a827989efa1 +Subproject commit 71f1abaaef67952928d831fb162fb20259713568 diff --git a/mobile/envoy_build_config/BUILD b/mobile/envoy_build_config/BUILD new file mode 100644 index 000000000000..779d1695d3b7 --- /dev/null +++ b/mobile/envoy_build_config/BUILD @@ -0,0 +1 @@ +licenses(["notice"]) # Apache 2 diff --git a/mobile/envoy_build_config/WORKSPACE b/mobile/envoy_build_config/WORKSPACE new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/mobile/envoy_build_config/extensions_build_config.bzl b/mobile/envoy_build_config/extensions_build_config.bzl new file mode 100644 index 000000000000..490cb47c38f7 --- /dev/null +++ b/mobile/envoy_build_config/extensions_build_config.bzl @@ -0,0 +1,5 @@ +EXTENSIONS = { + "envoy.filters.http.router": "//source/extensions/filters/http/router:config", + "envoy.filters.network.http_connection_manager": "//source/extensions/filters/network/http_connection_manager:config", +} +WINDOWS_EXTENSIONS = {} diff --git a/mobile/examples/common/config.yaml b/mobile/examples/common/config.yaml new file mode 100644 index 000000000000..b12466912530 --- /dev/null +++ b/mobile/examples/common/config.yaml @@ -0,0 +1,33 @@ +static_resources: + listeners: + - address: + socket_address: {address: 0.0.0.0, port_value: 9001, protocol: TCP} + filter_chains: + - filters: + - name: envoy.http_connection_manager + config: + stat_prefix: client + route_config: + virtual_hosts: + - name: all + domains: ["*"] + routes: + - match: {prefix: "/"} + route: {cluster: hello_world_api} + http_filters: + - name: envoy.router + clusters: + - name: hello_world_api + connect_timeout: 30s + dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: hello_world_api + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: {address: s3.amazonaws.com, port_value: 443} + tls_context: + sni: s3.amazonaws.com + type: LOGICAL_DNS diff --git a/mobile/examples/java/hello_world/AndroidManifest.xml b/mobile/examples/java/hello_world/AndroidManifest.xml new file mode 100644 index 000000000000..d3252ac19509 --- /dev/null +++ b/mobile/examples/java/hello_world/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + diff --git a/mobile/examples/java/hello_world/BUILD b/mobile/examples/java/hello_world/BUILD new file mode 100644 index 000000000000..853e9dd73e16 --- /dev/null +++ b/mobile/examples/java/hello_world/BUILD @@ -0,0 +1,19 @@ +licenses(["notice"]) # Apache 2 + +android_binary( + name = "hello_envoy", + srcs = glob([ + "MainActivity.java", + "Response.java", + "ResponseRecyclerViewAdapter.java", + "ResponseViewHolder.java", + ]), + custom_package = "io.envoyproxy.envoymobile.helloenvoy", + manifest = "AndroidManifest.xml", + resource_files = glob(["res/**"]), + deps = [ + "//dist:envoy_mobile_android", + "@androidsdk//com.android.support:appcompat-v7-25.0.0", + "@androidsdk//com.android.support:recyclerview-v7-25.0.0", + ], +) diff --git a/mobile/examples/java/hello_world/MainActivity.java b/mobile/examples/java/hello_world/MainActivity.java new file mode 100644 index 000000000000..f1b2ef07ddb8 --- /dev/null +++ b/mobile/examples/java/hello_world/MainActivity.java @@ -0,0 +1,117 @@ +package io.envoyproxy.envoymobile.helloenvoy; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.support.v7.widget.DividerItemDecoration; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.envoyproxy.envoymobile.Envoy; + +public class MainActivity extends Activity { + private static final String ENDPOINT = "http://0.0.0.0:9001/api.lyft.com/static/demo/hello_world.txt"; + private RecyclerView recyclerView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Envoy envoy = new Envoy(); + envoy.load(); + String config = null; + try { + config = loadEnvoyConfig(getBaseContext(), R.raw.config); + } catch (RuntimeException e) { + Log.d("MainActivity", "exception getting config.", e); + throw new RuntimeException("Can't get config to run envoy."); + } + envoy.run(getBaseContext(), config); + + recyclerView = (RecyclerView) findViewById(R.id.recycler_view); + recyclerView.setLayoutManager(new LinearLayoutManager(this)); + + final ResponseRecyclerViewAdapter adapter = new ResponseRecyclerViewAdapter(); + recyclerView.setAdapter(adapter); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL); + recyclerView.addItemDecoration(dividerItemDecoration); + HandlerThread thread = new HandlerThread(""); + thread.start(); + final Handler handler = new Handler(thread.getLooper()); + handler.postDelayed(new Runnable() { + @Override + public void run() { + try { + final Response response = makeRequest(); + recyclerView.post((Runnable) () -> adapter.add(response)); + } catch (IOException e) { + Log.d("MainActivity", "exception making request.", e); + } + // Make a call again + handler.postDelayed(this, TimeUnit.SECONDS.toMillis(10)); + } + }, 0); + } + + private Response makeRequest() throws IOException { + URL url = new URL(ENDPOINT); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + int status = connection.getResponseCode(); + if (status != 200) { + Log.d("MainActivity", "non 200 status: " + status); + } + + List serverHeaderField = connection.getHeaderFields().get("server"); + InputStream inputStream = connection.getInputStream(); + String body = deserialize(inputStream); + inputStream.close(); + return new Response(body, serverHeaderField != null ? String.join(", ", serverHeaderField) : ""); + } + + private String deserialize(InputStream inputStream) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + String line = bufferedReader.readLine(); + while (line != null) { + stringBuilder.append(line); + line = bufferedReader.readLine(); + } + bufferedReader.close(); + return stringBuilder.toString(); + } + + private String loadEnvoyConfig(Context context, int configResourceId) throws RuntimeException { + InputStream inputStream = context.getResources().openRawResource(configResourceId); + InputStreamReader inputReader = new InputStreamReader(inputStream); + BufferedReader bufReader = new BufferedReader(inputReader); + StringBuilder text = new StringBuilder(); + + try { + String line; + while ((line = bufReader.readLine()) != null) { + text.append(line); + text.append('\n'); + } + } catch (IOException e) { + return null; + } + return text.toString(); + } +} diff --git a/mobile/examples/java/hello_world/Response.java b/mobile/examples/java/hello_world/Response.java new file mode 100644 index 000000000000..cea4330b45df --- /dev/null +++ b/mobile/examples/java/hello_world/Response.java @@ -0,0 +1,13 @@ +package io.envoyproxy.envoymobile.helloenvoy; + +import java.util.List; + +public class Response { + public final String title; + public final String header; + + public Response(String title, String header) { + this.title = title; + this.header = header; + } +} diff --git a/mobile/examples/java/hello_world/ResponseRecyclerViewAdapter.java b/mobile/examples/java/hello_world/ResponseRecyclerViewAdapter.java new file mode 100644 index 000000000000..dd5aed24d09d --- /dev/null +++ b/mobile/examples/java/hello_world/ResponseRecyclerViewAdapter.java @@ -0,0 +1,37 @@ +package io.envoyproxy.envoymobile.helloenvoy; + +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +public class ResponseRecyclerViewAdapter extends RecyclerView.Adapter { + private final List data = new ArrayList<>(); + + @Override + public ResponseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + Context context = parent.getContext(); + LayoutInflater inflater = LayoutInflater.from(context); + View view = inflater.inflate(R.layout.item, parent, false); + return new ResponseViewHolder(view); + } + + @Override + public void onBindViewHolder(ResponseViewHolder holder, int position) { + holder.setResult(data.get(position)); + } + + @Override + public int getItemCount() { + return data.size(); + } + + public void add(Response response) { + data.add(0, response); + notifyItemInserted(0); + } +} diff --git a/mobile/examples/java/hello_world/ResponseViewHolder.java b/mobile/examples/java/hello_world/ResponseViewHolder.java new file mode 100644 index 000000000000..6addf14b4337 --- /dev/null +++ b/mobile/examples/java/hello_world/ResponseViewHolder.java @@ -0,0 +1,22 @@ +package io.envoyproxy.envoymobile.helloenvoy; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +public class ResponseViewHolder extends RecyclerView.ViewHolder { + private final TextView responseTextView; + private final TextView headerTextView; + + public ResponseViewHolder(View itemView) { + super(itemView); + this.responseTextView = (TextView) itemView.findViewById(R.id.response_text_view); + this.headerTextView = (TextView) itemView.findViewById(R.id.header_text_view); + } + + public void setResult(Response response) { + responseTextView.setText(responseTextView.getResources().getString(R.string.title_string, response.title)); + headerTextView.setText(headerTextView.getResources().getString(R.string.header_string, response.header)); + } + +} diff --git a/mobile/examples/java/hello_world/res/layout/activity_main.xml b/mobile/examples/java/hello_world/res/layout/activity_main.xml new file mode 100644 index 000000000000..7ad0c41429d4 --- /dev/null +++ b/mobile/examples/java/hello_world/res/layout/activity_main.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/mobile/examples/java/hello_world/res/layout/item.xml b/mobile/examples/java/hello_world/res/layout/item.xml new file mode 100644 index 000000000000..5746718d459d --- /dev/null +++ b/mobile/examples/java/hello_world/res/layout/item.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/mobile/examples/java/hello_world/res/raw/config.yaml b/mobile/examples/java/hello_world/res/raw/config.yaml new file mode 120000 index 000000000000..8d323c813c8c --- /dev/null +++ b/mobile/examples/java/hello_world/res/raw/config.yaml @@ -0,0 +1 @@ +../../../../common/config.yaml \ No newline at end of file diff --git a/mobile/examples/java/hello_world/res/values/colors.xml b/mobile/examples/java/hello_world/res/values/colors.xml new file mode 100644 index 000000000000..77e6b5b5e3c8 --- /dev/null +++ b/mobile/examples/java/hello_world/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #d3bc3a + diff --git a/mobile/examples/java/hello_world/res/values/strings.xml b/mobile/examples/java/hello_world/res/values/strings.xml new file mode 100644 index 000000000000..77cc7a52c9b0 --- /dev/null +++ b/mobile/examples/java/hello_world/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Response: %s + \'Server\' header: %s + diff --git a/mobile/examples/java/hello_world/res/values/styles.xml b/mobile/examples/java/hello_world/res/values/styles.xml new file mode 100644 index 000000000000..7bb1747cd2f4 --- /dev/null +++ b/mobile/examples/java/hello_world/res/values/styles.xml @@ -0,0 +1,6 @@ + + + + diff --git a/mobile/examples/objective-c/hello_world/AppDelegate.h b/mobile/examples/objective-c/hello_world/AppDelegate.h new file mode 100644 index 000000000000..e1fe1bf304bb --- /dev/null +++ b/mobile/examples/objective-c/hello_world/AppDelegate.h @@ -0,0 +1,8 @@ +#import +#import + +@interface AppDelegate : UIResponder + +@property (strong, nonatomic) UIWindow *window; + +@end diff --git a/mobile/examples/objective-c/hello_world/AppDelegate.mm b/mobile/examples/objective-c/hello_world/AppDelegate.mm new file mode 100644 index 000000000000..421390fd0a50 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/AppDelegate.mm @@ -0,0 +1,38 @@ +#import "AppDelegate.h" +#import +#import +#import "ViewController.h" + +@interface AppDelegate () +@end + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + UIViewController *controller = [ViewController new]; + self.window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds]; + [self.window setRootViewController:controller]; + [self.window makeKeyAndVisible]; + + [NSThread detachNewThreadSelector:@selector(startEnvoy) toTarget:self withObject:nil]; + NSLog(@"Finished launching!"); + return YES; +} + +- (void)startEnvoy { + NSString *configFile = [[NSBundle mainBundle] pathForResource:@"config" ofType:@"yaml"]; + NSString *configYaml = [NSString stringWithContentsOfFile:configFile encoding:NSUTF8StringEncoding error:NULL]; + NSLog(@"Loading config:\n%@", configYaml); + + // Initialize the server's main context under a try/catch loop and simply return EXIT_FAILURE + // as needed. Whatever code in the initialization path that fails is expected to log an error + // message so the user can diagnose. + try { + run_envoy(configYaml.UTF8String); + } catch (NSException *e) { + NSLog(@"Error starting Envoy: %@", e); + exit(EXIT_FAILURE); + } +} + +@end diff --git a/mobile/examples/objective-c/hello_world/BUILD b/mobile/examples/objective-c/hello_world/BUILD new file mode 100644 index 000000000000..723f57f65259 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/BUILD @@ -0,0 +1,25 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application", "ios_framework", "ios_static_framework") + +envoy_package() + +objc_library( + name = "appmain", + srcs = glob([ + "*.h", + "*.mm", + ]), + data = ["config.yaml"], + deps = ["//dist:envoy_mobile_ios"], +) + +ios_application( + name = "app", + bundle_id = "io.envoyproxy.envoymobile.helloworld", + families = ["iphone"], + infoplists = ["Info.plist"], + minimum_os_version = "10.0", + deps = ["appmain"], +) diff --git a/mobile/examples/objective-c/hello_world/Info.plist b/mobile/examples/objective-c/hello_world/Info.plist new file mode 100644 index 000000000000..a6b1bdb725fa --- /dev/null +++ b/mobile/examples/objective-c/hello_world/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreenView + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + + diff --git a/mobile/examples/objective-c/hello_world/README.md b/mobile/examples/objective-c/hello_world/README.md new file mode 100644 index 000000000000..8a2dee08e833 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/README.md @@ -0,0 +1,5 @@ +# Hello World example + +This example project starts the Envoy process and uses it as a proxy for listening in on requests being made to a "hello world" endpoint on a timer. + +Upon receiving a response, the details are logged to the console, and the response text and `Server` header are displayed in a table view in a native iOS app. diff --git a/mobile/examples/objective-c/hello_world/ViewController.h b/mobile/examples/objective-c/hello_world/ViewController.h new file mode 100644 index 000000000000..922e5721e8f2 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/ViewController.h @@ -0,0 +1,4 @@ +#import + +@interface ViewController: UITableViewController +@end diff --git a/mobile/examples/objective-c/hello_world/ViewController.mm b/mobile/examples/objective-c/hello_world/ViewController.mm new file mode 100644 index 000000000000..b4027ecf06e2 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/ViewController.mm @@ -0,0 +1,113 @@ +#import +#import "ViewController.h" + +#pragma mark - Constants + +NSString *_CELL_ID = @"cell-id"; +NSString *_ENDPOINT = @"http://0.0.0.0:9001/api.lyft.com/static/demo/hello_world.txt"; + +#pragma mark - ResponseValue + +@interface ResponseValue: NSObject +@property (nonatomic, strong) NSString *body; +@property (nonatomic, strong) NSString *serverHeader; +@end + +@implementation ResponseValue +@end + +#pragma mark - ViewController + +@interface ViewController () +@property (nonatomic, strong) NSMutableArray *responses; +@property (nonatomic, weak) NSTimer *requestTimer; +@end + +@implementation ViewController + +#pragma mark - Lifecycle + +- (instancetype)init { + self = [super init]; + if (self) { + self.responses = [NSMutableArray new]; + self.tableView.allowsSelection = FALSE; + } + return self; +} + +- (void)dealloc { + [self.requestTimer invalidate]; + self.requestTimer = nil; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + [self startRequests]; +} + +#pragma mark - Requests + +- (void)startRequests { + // Note that the first delay will give Envoy time to start up. + self.requestTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(performRequest) userInfo:nil repeats:true]; +} + +- (void)performRequest { + NSURLSession *session = [NSURLSession sharedSession]; + NSURL *url = [NSURL URLWithString:_ENDPOINT]; + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSLog(@"Starting request to '%@'", url.path); + + __weak ViewController *weakSelf = self; + NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { + if (error == nil && [(NSHTTPURLResponse *)response statusCode] == 200) { + [weakSelf handleResponse:(NSHTTPURLResponse *)response data:data]; + } else { + NSLog(@"Received error: %@", error); + } + }]; + [task resume]; +} + +- (void)handleResponse:(NSHTTPURLResponse *)response data:(NSData *)data { + NSString *body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + if (body == nil || response == nil) { + NSLog(@"Failed to deserialize response string"); + return; + } + + ResponseValue *value = [ResponseValue new]; + value.body = body; + value.serverHeader = [[response allHeaderFields] valueForKey:@"Server"]; + + NSLog(@"Response:\n%ld bytes\n%@\n%@", data.length, body, [response allHeaderFields]); + dispatch_async(dispatch_get_main_queue(), ^{ + [self.responses addObject:value]; + [self.tableView reloadData]; + }); +} + +#pragma mark - UITableView + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return 1; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return self.responses.count; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_CELL_ID]; + if (cell == nil) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:_CELL_ID]; + } + + ResponseValue *response = self.responses[indexPath.row]; + cell.textLabel.text = [NSString stringWithFormat:@"Response: %@", response.body]; + cell.detailTextLabel.text = [NSString stringWithFormat:@"'Server' header: %@", response.serverHeader]; + return cell; +} + +@end diff --git a/mobile/examples/objective-c/hello_world/config.yaml b/mobile/examples/objective-c/hello_world/config.yaml new file mode 120000 index 000000000000..d78ec6edb730 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/config.yaml @@ -0,0 +1 @@ +../../common/config.yaml \ No newline at end of file diff --git a/mobile/examples/objective-c/hello_world/main.mm b/mobile/examples/objective-c/hello_world/main.mm new file mode 100644 index 000000000000..f9cb9b46ec70 --- /dev/null +++ b/mobile/examples/objective-c/hello_world/main.mm @@ -0,0 +1,7 @@ +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/mobile/examples/swift/hello_world/AppDelegate.swift b/mobile/examples/swift/hello_world/AppDelegate.swift new file mode 100644 index 000000000000..fc75d233e5a8 --- /dev/null +++ b/mobile/examples/swift/hello_world/AppDelegate.swift @@ -0,0 +1,42 @@ +import Envoy +import UIKit + +private enum ConfigLoadError: Error { + case noFileAtPath +} + +@UIApplicationMain +final class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + func application(_ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) + -> Bool + { + do { + let configYaml = try self.loadEnvoyConfig() as NSString + NSLog("Loading config:\n\(configYaml)") + Thread.detachNewThread { + run_envoy(configYaml.utf8String) + } + } catch let error { + NSLog("Failed to load config: \(error)") + } + + let window = UIWindow(frame: UIScreen.main.bounds) + window.rootViewController = ViewController() + window.makeKeyAndVisible() + self.window = window + NSLog("Finished launching!") + + return true + } + + private func loadEnvoyConfig() throws -> String { + guard let configFile = Bundle.main.path(forResource: "config", ofType: "yaml") else { + throw ConfigLoadError.noFileAtPath + } + + return try String(contentsOfFile: configFile, encoding: .utf8) + } +} diff --git a/mobile/examples/swift/hello_world/BUILD b/mobile/examples/swift/hello_world/BUILD new file mode 100644 index 000000000000..523db960510c --- /dev/null +++ b/mobile/examples/swift/hello_world/BUILD @@ -0,0 +1,24 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy//bazel:envoy_build_system.bzl", "envoy_package") +load("@build_bazel_rules_apple//apple:ios.bzl", "ios_application") + +envoy_package() + +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "appmain", + srcs = glob(["*.swift"]), + data = ["config.yaml"], + deps = ["//dist:envoy_mobile_ios"], +) + +ios_application( + name = "app", + bundle_id = "io.envoyproxy.envoymobile.helloworld", + families = ["iphone"], + infoplists = ["Info.plist"], + minimum_os_version = "10.0", + deps = ["appmain"], +) diff --git a/mobile/examples/swift/hello_world/Info.plist b/mobile/examples/swift/hello_world/Info.plist new file mode 100644 index 000000000000..a6b1bdb725fa --- /dev/null +++ b/mobile/examples/swift/hello_world/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + ITSAppUsesNonExemptEncryption + + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreenView + UIRequiredDeviceCapabilities + + armv7 + + UIStatusBarStyle + UIStatusBarStyleLightContent + + diff --git a/mobile/examples/swift/hello_world/README.md b/mobile/examples/swift/hello_world/README.md new file mode 100644 index 000000000000..8a2dee08e833 --- /dev/null +++ b/mobile/examples/swift/hello_world/README.md @@ -0,0 +1,5 @@ +# Hello World example + +This example project starts the Envoy process and uses it as a proxy for listening in on requests being made to a "hello world" endpoint on a timer. + +Upon receiving a response, the details are logged to the console, and the response text and `Server` header are displayed in a table view in a native iOS app. diff --git a/mobile/examples/swift/hello_world/ResponseValue.swift b/mobile/examples/swift/hello_world/ResponseValue.swift new file mode 100644 index 000000000000..a08459cd9bed --- /dev/null +++ b/mobile/examples/swift/hello_world/ResponseValue.swift @@ -0,0 +1,4 @@ +struct Response { + let body: String + let serverHeader: String +} diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift new file mode 100644 index 000000000000..3914d0ed111e --- /dev/null +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -0,0 +1,82 @@ +import UIKit + +private let kCellID = "cell-id" +private let kURL = URL(string: "http://0.0.0.0:9001/api.lyft.com/static/demo/hello_world.txt")! + +final class ViewController: UITableViewController { + private var responses = [Response]() + private var timer: Timer? + + override func viewDidLoad() { + super.viewDidLoad() + self.startRequests() + } + + deinit { + self.timer?.invalidate() + } + + // MARK: - Requests + + private func startRequests() { + // Note that the first delay will give Envoy time to start up. + self.timer = .scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in + self?.performRequest() + } + } + + private func performRequest() { + let request = URLRequest(url: kURL) + NSLog("Starting request to '\(kURL.path)") + let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in + if let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data { + self?.handle(response: response, with: data) + } else if let error = error { + NSLog("Received error: \(error)") + } else { + NSLog("Failed to receive data from the server") + } + } + + task.resume() + } + + private func handle(response: HTTPURLResponse, with data: Data) { + guard let body = String(data: data, encoding: .utf8) else { + return NSLog("Failed to deserialize response string") + } + + let untypedHeaders = response.allHeaderFields + let headers = Dictionary(uniqueKeysWithValues: untypedHeaders.map { header -> (String, String) in + return (header.key as? String ?? String(describing: header.key), "\(header.value)") + }) + + NSLog("Response:\n\(data.count) bytes\n\(body)\n\(headers)") + + let value = Response(body: body, serverHeader: headers["Server"] ?? "") + DispatchQueue.main.async { + self.responses.append(value) + self.tableView.reloadData() + } + } + + // MARK: - UITableView + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.responses.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: kCellID) ?? + UITableViewCell(style: .subtitle, reuseIdentifier: kCellID) + + let response = self.responses[indexPath.row] + cell.textLabel?.text = "Response: \(response.body)" + cell.detailTextLabel?.text = "'Server' header: \(response.serverHeader)" + return cell + } +} diff --git a/mobile/examples/swift/hello_world/config.yaml b/mobile/examples/swift/hello_world/config.yaml new file mode 120000 index 000000000000..d78ec6edb730 --- /dev/null +++ b/mobile/examples/swift/hello_world/config.yaml @@ -0,0 +1 @@ +../../common/config.yaml \ No newline at end of file diff --git a/mobile/library/EnvoyManifest.xml b/mobile/library/EnvoyManifest.xml new file mode 100644 index 000000000000..ab0e6770e159 --- /dev/null +++ b/mobile/library/EnvoyManifest.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/mobile/library/common/BUILD b/mobile/library/common/BUILD new file mode 100644 index 000000000000..681c3b034d3e --- /dev/null +++ b/mobile/library/common/BUILD @@ -0,0 +1,24 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_package") + +envoy_package() + +envoy_cc_library( + name = "envoy_main_interface_lib", + srcs = ["main_interface.cc"], + hdrs = ["main_interface.h"], + repository = "@envoy", + deps = ["@envoy//source/exe:envoy_main_common_lib"], +) + +cc_library( + name = "envoy_jni_interface_lib", + srcs = ["jni_interface.cc"], + copts = ["-std=c++14"], + linkopts = [ + "-lm", + "-llog", + ], + deps = [":envoy_main_interface_lib"], +) diff --git a/mobile/library/common/jni_interface.cc b/mobile/library/common/jni_interface.cc new file mode 100644 index 000000000000..d052c78c7f29 --- /dev/null +++ b/mobile/library/common/jni_interface.cc @@ -0,0 +1,38 @@ +#include +#include +#include + +#include "main_interface.h" + +// NOLINT(namespace-envoy) + +JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env = nullptr; + + if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) { + return -1; + } + + ares_library_init_jvm(vm); + return JNI_VERSION_1_6; +} + +extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_Envoy_runEnvoy(JNIEnv* env, + jobject, // this + jstring config) { + return run_envoy(env->GetStringUTFChars(config, nullptr)); +} + +extern "C" JNIEXPORT jint JNICALL +Java_io_envoyproxy_envoymobile_Envoy_initialize(JNIEnv* env, + jobject, // this + jobject connectivity_manager) { + return ares_library_init_android(connectivity_manager); +} + +extern "C" JNIEXPORT jboolean JNICALL Java_io_envoyproxy_envoymobile_Envoy_isAresInitialized( + JNIEnv* env, + jobject // this +) { + return ares_library_android_initialized() == ARES_SUCCESS; +} diff --git a/mobile/library/common/main_interface.cc b/mobile/library/common/main_interface.cc new file mode 100644 index 000000000000..ee6434649f47 --- /dev/null +++ b/mobile/library/common/main_interface.cc @@ -0,0 +1,50 @@ +#include "library/common/main_interface.h" + +#include "common/upstream/logical_dns_cluster.h" + +#include "exe/main_common.h" + +#include "extensions/filters/http/router/config.h" +#include "extensions/filters/network/http_connection_manager/config.h" +#include "extensions/transport_sockets/raw_buffer/config.h" +#include "extensions/transport_sockets/tls/config.h" + +// NOLINT(namespace-envoy) + +/** + * External entrypoint for library. + */ +extern "C" int run_envoy(const char* config) { + std::unique_ptr main_common; + + char* envoy_argv[] = {strdup("envoy"), strdup("--config-yaml"), strdup(config), nullptr}; + + // Ensure static factory registration occurs on time. + Envoy::Extensions::HttpFilters::RouterFilter::forceRegisterRouterFilterConfig(); + Envoy::Extensions::NetworkFilters::HttpConnectionManager:: + forceRegisterHttpConnectionManagerFilterConfigFactory(); + Envoy::Extensions::TransportSockets::RawBuffer::forceRegisterDownstreamRawBufferSocketFactory(); + Envoy::Extensions::TransportSockets::RawBuffer::forceRegisterUpstreamRawBufferSocketFactory(); + Envoy::Extensions::TransportSockets::Tls::forceRegisterUpstreamSslSocketFactory(); + Envoy::Upstream::forceRegisterLogicalDnsClusterFactory(); + + // Initialize the server's main context under a try/catch loop and simply + // return EXIT_FAILURE as needed. Whatever code in the initialization path + // that fails is expected to log an error message so the user can diagnose. + try { + main_common = std::make_unique(3, envoy_argv); + } catch (const Envoy::NoServingException& e) { + return EXIT_SUCCESS; + } catch (const Envoy::MalformedArgvException& e) { + std::cerr << e.what() << std::endl; + + return EXIT_FAILURE; + } catch (const Envoy::EnvoyException& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + // Run the server listener loop outside try/catch blocks, so that unexpected + // exceptions show up as a core-dumps for easier diagnostics. + return main_common->run() ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/mobile/library/common/main_interface.h b/mobile/library/common/main_interface.h new file mode 100644 index 000000000000..42232fb1e889 --- /dev/null +++ b/mobile/library/common/main_interface.h @@ -0,0 +1,12 @@ +#pragma once + +// NOLINT(namespace-envoy) + +/** + * External entrypoint for library. + */ +#ifdef __cplusplus +extern "C" int run_envoy(const char* config); +#else +int run_envoy(const char* config); +#endif diff --git a/mobile/library/java/io/envoyproxy/envoymobile/Envoy.java b/mobile/library/java/io/envoyproxy/envoymobile/Envoy.java new file mode 100644 index 000000000000..50d798ac8749 --- /dev/null +++ b/mobile/library/java/io/envoyproxy/envoymobile/Envoy.java @@ -0,0 +1,31 @@ +package io.envoyproxy.envoymobile; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.util.Log; + +import java.io.*; + +public class Envoy { + + public void load() { + System.loadLibrary("envoy_jni"); + } + + public void run(Context context, String config) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + initialize((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)); + runEnvoy(config.trim()); + } + }); + thread.start(); + } + + private native int initialize(ConnectivityManager connectivityManager); + + private native boolean isAresInitialized(); + + private native int runEnvoy(String config); +} diff --git a/mobile/tools/check_format.sh b/mobile/tools/check_format.sh index cc6b2276eb1c..5fe4ca27231f 100755 --- a/mobile/tools/check_format.sh +++ b/mobile/tools/check_format.sh @@ -4,5 +4,18 @@ set -e export BUILDIFIER_BIN="/usr/local/bin/buildifier" -envoy/tools/check_format.py --add-excluded-prefixes=./envoy/ check +ENVOY_FORMAT_ACTION="$1" +if [ -z "$ENVOY_FORMAT_ACTION" ]; then + echo "No action specified, defaulting to check" + ENVOY_FORMAT_ACTION="check" +fi + +# TODO(mattklein123): WORKSPACE is excluded due to warning about @bazel_tools reference. Fix here +# or in the upstream checker. +# TODO(mattklein123): Objective-C is excluded because the clang-format setup is not correct. Fix. +# TODO(mattklein123): We don't need envoy_package() in various files. Somehow fix in upstream +# checker. +envoy/tools/check_format.py \ + --add-excluded-prefixes ./envoy/ ./envoy_build_config/extensions_build_config.bzl ./WORKSPACE ./examples/objective-c/ \ + --skip_envoy_build_rule_check "$ENVOY_FORMAT_ACTION" envoy/tools/format_python_tools.sh check