Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions advanced.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ services:
image: grpcweb/binary-client
ports:
- "8081:8081"
angular-client:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/angular_client/Dockerfile
depends_on:
- prereqs
image: grpcweb/angular-client
ports:
- "8081:8081"
185 changes: 185 additions & 0 deletions javascript/net/grpc/web/grpc_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,26 @@ void ReplaceCharacters(string *s, const char *remove, char replacewith) {
}
}

// Returns name in PascalCase to SCREAMING_SNAKE_CASE.
// e.g 'EchoServiceConfig' -> 'ECHO_SERVICE_CONFIG'
string PascalToConstCase(const string& s) {
if (s.empty()) {
return s;
}
// Ignore the first character. It's already uppercase and doesn't need an
// underscore.
string result;
result.push_back(s[0]);
for (int i = 1; i < s.size(); ++i) {
if (s[i] >= 'A' && s[i] <= 'Z') {
result.push_back('_');
result.push_back(s[i]);
} else {
result.push_back(s[i] - 'a' + 'A');
}
}
return result;
}

// The following function was copied from
// google/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc
Expand Down Expand Up @@ -633,6 +653,149 @@ void PrintES6Imports(Printer* printer, const FileDescriptor* file) {
printer->Print(vars, "} from './$base_name$_pb';\n\n");
}

void PrintAngularServiceImports(Printer* printer, const FileDescriptor* file,
const string base_file_name) {
printer->Print(
"import {Injectable, InjectionToken, Inject} from '@angular/core';\n");
printer->Print("import {Observable} from 'rxjs';\n\n");

if (!file->service_count()) {
return;
}

std::map<string, string> vars;
vars["base_file_name"] = base_file_name;

const ServiceDescriptor* service = file->service(0);
vars["service_name"] = service->name();

if (file->service_count() == 1) {
printer->Print(
vars, "import {$service_name$Client} from './$base_file_name$';\n\n");
return;
}

printer->Print("import {\n");
printer->Indent();
printer->Print(vars, "$service_name$");

for (int service_index = 1; service_index < file->service_count();
++service_index) {
const ServiceDescriptor* service = file->service(service_index);
vars["service_name"] = service->name();
printer->Print(vars, ",\n$service_name$Client");
}

printer->Outdent();
printer->Print(vars, "} from './$base_file_name$';\n\n");
}

void PrintAngularServiceMethod(Printer* printer, const MethodDescriptor* method,
std::map<string, string> vars) {
if (!method->client_streaming()) {
printer->Print(vars,
"$js_method_name$(\n"
" request: $input_type$,\n"
" metadata?: grpcWeb.Metadata\n"
"): Observable<$output_type$> {\n");
printer->Indent();
printer->Print("return new Observable(subscriber => {\n");
printer->Indent();

if (method->server_streaming()) {
vars["stream_object"] = "stream";
printer->Print(
vars,
"const $stream_object$ = this.client.$js_method_name$(request, "
"metadata);\n"
"$stream_object$.on('data', (response) => "
"subscriber.next(response));\n"
"$stream_object$.on('error', (err) => subscriber.error(err));\n"
"$stream_object$.on('status', (status) => {\n"
" if (status.code != grpcWeb.StatusCode.OK) {\n"
" subscriber.error({code: status.code, message: "
"status.details});\n"
" }\n"
"});\n"
"$stream_object$.on('end', () => subscriber.complete());\n");
} else {
vars["stream_object"] = "call";
printer->Print(
vars,
"const callback = (err, response) => {\n"
" if (err) {\n"
" subscriber.error(err);\n"
" } else {\n"
" subscriber.next(response);\n"
" subscriber.complete();\n"
" }\n"
"};\n"
"const $stream_object$ = this.client.$js_method_name$(request, "
"metadata, callback);\n");
}
printer->Print(vars,
"return () => { if ($stream_object$) { "
"$stream_object$.cancel(); } }\n");
printer->Outdent();
printer->Print("});\n");
printer->Outdent();
printer->Print("}\n\n");
}
}

void PrintAngularServiceClass(Printer* printer, const FileDescriptor* file) {
for (int service_index = 0; service_index < file->service_count();
++service_index) {
std::map<string, string> vars;
const ServiceDescriptor* service = file->service(service_index);
vars["service_name"] = service->name();
printer->Print(vars, "export interface $service_name$Config {\n");
printer->Print(
" hostname: string;\n"
" credentials: null | { [index: string]: string; };\n"
" options: null | { [index: string]: string; };\n");
printer->Print("};\n");
vars["config_name"] = PascalToConstCase(service->name()) + "_CONFIG";
printer->Print(
vars,
"export const $config_name$ = new "
"InjectionToken<$service_name$Config>('$config_name$');\n\n");

printer->Print(
"@Injectable({\n"
" providedIn: 'root',\n"
"})\n");
printer->Print(vars, "export class $service_name$ {\n");
printer->Indent();
printer->Print(vars,
"private client: $service_name$Client;\n\n"
"constructor(@Inject($config_name$) private config: "
"$service_name$Config) {\n"
" this.client = new $service_name$Client(\n"
" this.config.hostname,\n"
" this.config.credentials,\n"
" this.config.options);\n"
"}\n\n");
for (int method_index = 0; method_index < service->method_count();
++method_index) {
const MethodDescriptor* method = service->method(method_index);
vars["js_method_name"] = LowercaseFirstLetter(method->name());
vars["input_type"] = JSMessageType(method->input_type(), file);
vars["output_type"] = JSMessageType(method->output_type(), file);
PrintAngularServiceMethod(printer, method, vars);
}
printer->Outdent();
printer->Print("}\n");
}
}

void PrintAngularServiceFile(Printer* printer, const FileDescriptor* file,
const string base_file_name) {
PrintAngularServiceImports(printer, file, base_file_name);
PrintES6Imports(printer, file);
PrintAngularServiceClass(printer, file);
}

void PrintTypescriptFile(Printer* printer, const FileDescriptor* file,
std::map<string, string> vars) {
PrintES6Imports(printer, file);
Expand Down Expand Up @@ -1309,6 +1472,7 @@ class GrpcCodeGenerator : public CodeGenerator {
ImportStyle import_style;
bool generate_dts = false;
bool gen_multiple_files = false;
bool generate_angular = false;

for (size_t i = 0; i < options.size(); ++i) {
if (options[i].first == "out") {
Expand All @@ -1317,6 +1481,8 @@ class GrpcCodeGenerator : public CodeGenerator {
mode = options[i].second;
} else if (options[i].first == "import_style") {
import_style_str = options[i].second;
} else if (options[i].first == "framework") {
generate_angular = options[i].second == "angular";
} else if (options[i].first == "multiple_files") {
gen_multiple_files = options[i].second == "true";
} else {
Expand Down Expand Up @@ -1357,6 +1523,10 @@ class GrpcCodeGenerator : public CodeGenerator {
import_style = ImportStyle::CLOSURE;
} else if (import_style_str == "commonjs") {
import_style = ImportStyle::COMMONJS;
// Generate Typescript declarations for Angular.
if (generate_angular) {
generate_dts = true;
}
} else if (import_style_str == "commonjs+dts") {
import_style = ImportStyle::COMMONJS;
generate_dts = true;
Expand Down Expand Up @@ -1559,6 +1729,21 @@ class GrpcCodeGenerator : public CodeGenerator {
PrintGrpcWebDtsFile(&grpcweb_dts_printer, file);
}

if (generate_angular && import_style != ImportStyle::CLOSURE) {
string angular_service_file_name =
StripProto(file->name()) + ".service.pb.ts";

std::unique_ptr<ZeroCopyOutputStream> angular_service_output(
context->Open(angular_service_file_name));
Printer angular_service_printer(angular_service_output.get(), '$');

PrintFileHeader(&angular_service_printer, vars);

const string base_file_name = StripSuffixString(
file_name, import_style == TYPESCRIPT ? ".ts" : ".js");
PrintAngularServiceFile(&angular_service_printer, file, base_file_name);
}

return true;
}
};
Expand Down
36 changes: 36 additions & 0 deletions net/grpc/gateway/docker/angular_client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright 2018 Google LLC
#
# 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
#
# https://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.

FROM grpcweb/prereqs

ARG EXAMPLE_DIR=/github/grpc-web/net/grpc/gateway/examples/echo
ARG ANGULAR_SERVICE_DIR=$EXAMPLE_DIR/angular-example/src/app/shared

RUN npm install -g @angular/cli

RUN curl -o chrome.deb -sSL https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
RUN dpkg -i chrome.deb; apt-get -fy install

RUN protoc -I=$EXAMPLE_DIR echo.proto \
--js_out=import_style=commonjs:$ANGULAR_SERVICE_DIR \
--grpc-web_out=import_style=commonjs+dts,mode=grpcwebtext,framework=angular:$ANGULAR_SERVICE_DIR

RUN cd $EXAMPLE_DIR/angular-example && \
npm install && \
ng build --prod && \
cp dist/angular-example/* /var/www/html

EXPOSE 8081
WORKDIR /var/www/html
CMD ["python", "-m", "SimpleHTTPServer", "8081"]
2 changes: 1 addition & 1 deletion net/grpc/gateway/docker/common/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

FROM node:8-stretch
FROM node:10-stretch
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required for @angular/cli to work.


RUN apt-get -qq update && apt-get -qq install -y \
unzip
Expand Down
48 changes: 48 additions & 0 deletions net/grpc/gateway/examples/echo/angular-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.

# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out

# dependencies
/node_modules
package-lock.json
yarn.lock

# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json

# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace

# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*

# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings

# System Files
.DS_Store
Thumbs.db
27 changes: 27 additions & 0 deletions net/grpc/gateway/examples/echo/angular-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# AngularExample

This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.0.

## Development server

Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.

## Code scaffolding

Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.

## Build

Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.

## Running unit tests

Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).

## Running end-to-end tests

Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).

## Further help

To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
Loading