![]() |
||
| protobuf | grpc | starlark |
grpc-starlark is a:
- library for embedding a gRPC-capable starlark interpreter,
- standalone binary
grpcstarthat executes starlark scripts.
The author pronounces this as
grip-ster(like "napster", but you can say it however you like).
grpcstar use cases include:
- replacement for
grpcurlwhen calling gRPC services from the command line - stand-in for
postman - testing gRPC backends
- mocking gRPC backends in integration tests
- gRPC microservices with things like Google Cloud Run
Download a binary from the releases page, or install from source:
go install github.com/stackb/grpc-starlark/cmd/grpcstar@latestusage: grpcstar [OPTIONS...] [ARGS...]
github:
https://github.com/stackb/grpc-starlark
options:
-h, --help [optional, false]
show this help screen
-p, --protoset [required]
filename of proto descriptor set
-f, --file [required]
filename of entrypoint starlark script
(conventionally named *.grpc.star)
-e, --entrypoint [optional, "main"]
name of function in global scope to invoke upon script start
-o, --output [optional, "json", oneof "json|proto|text|yaml"]
formatter for output protobufs returned by entrypoint function
-i, --interactive [optional, false]
start a REPL session (rather then exec the entrypoint)
example:
$ grpcstar \
-p routeguide.pb \
-f routeguide.grpc.star \
-e call_get_feature \
longitude=35.0 latitude=109.1
An image is pushed to ghcr.io/stackb/grpc-starlark/grpcstar during the release
workflow. It consists of small base layer and the grpcstar binary at the root
of the container with the Entrypoint to set /grpcstar.
FROM ghcr.io/stackb/grpc-starlark/grpcstar:v0.6.0
COPY service.descriptor.pb /
COPY server.grpc.star /
CMD --protoset /service.descriptor.pb --file /server.grpc.stargrpcstar requires a precompiled proto descriptor set via the --protoset (-p)
flag. This file defines the universe of message, enum, and service types that
can be used in your script.
This file can be generated by the protoc --descriptor_set_out flag and is used
by other tools in the protobuf/gRPC ecosystem (see
grpcurl).
For bazel users, the proto_library rule produces this as its output file. The
proto_descriptor_set
concatenates multiple descriptor sets together (cat foo.descriptor.pb bar.descriptor.pb > combined.descriptor.pb).
The script file --file (-f) is the entrypoint file executed by the embedded starlark interpreter.
Use load statements (e.g. load("filename{.star}", "symbol")) to populate
additional symbols into the entrypoint file.
The script must contain a function named main that takes a single
positional argument ctx (e.g.def main(ctx):). The --entrypoint (-e)
flag can be used to override this.
The ctx is a struct; ctx.vars holds key-value pairs that can be set on the
command line (e.g. name=foo would satisfy ctx.vars.name == 'foo').
The entrypoint function can either return nothing (None) or a list of protobuf
messages. The messages will be printed to stdout and formatted according the
the --output flag (-o). Choose one of json, proto, text, or yaml;
default is json.
print(...) statements are sent to stderr.
The starlark interpreter starts a single main thread for the top-level
entrypoint file. Each invocation of a grpc.Server handler callback function
is run concurrently in a new thread. thread.defer callbacks also occur in a
new thread.
grpc-starlark is implemented using go and has an API similar to grpc-go.
The message and enum types are available via the proto.package function:
pb = proto.package("example.routeguide")
print(pb.Rectangle)These define "strongly-typed" structs for use in creating and interacting with protobuf messages:
colorado = pb.Rectangle(
lo = pb.Point(latitude = 36.999, longitude = -109.045),
hi = pb.Point(latitude = 40.979, longitude = -102.051),
)For more details see github.com/stripe/skycfg, which provides the core protobuf functionality.
Use the grpc.Server constructor to make a new server. Use the register
function to provide function implementations for the service handlers. Example:
server = grpc.Server()
server.register("example.routeguide.RouteGuide", {
"GetFeature": get_feature,
"ListFeatures": list_features,
"RecordRoute": record_route,
"RouteChat": route_chat,
})Use a net.Listener to bind the server to a network address:
listener = net.Listener(address = "localhost:8080")To bind to a free port, use the defaults (localhost is the host and 0 is
the port)
listener = net.Listener()
print(listener.address) # localhost:50234def get_feature(stream, point):
"""get_feature implements a unary method handler
Args:
stream: the stream object
point: the requested Point
Returns:
a Feature, ideally nearest to the given point.
"""
return pb.Feature(name = "point (%d,%d)" % (point.longitude, point.latitude))The stream object can be used to access incoming headers stream.ctx.metadata
or set outgoing headers/trailers (stream.set_header, stream.set_trailer).
The second positional argument is the request message.
The function should return an appropriate response message or a grpc.Error
using an status code and message (e.g. return grpc.Error(code = grpc.status.UNAUTHENTICATED, message = "authorization header is required")))
def list_features(stream, rect):
"""list_features implements a server streaming handler
Args:
stream: the stream object
rect: the rectangle to get features within
Returns:
None
"""
features = [
pb.Feature(name = "lo (%d,%d)" % (rect.lo.longitude, rect.lo.latitude)),
pb.Feature(name = "hi (%d,%d)" % (rect.lo.longitude, rect.hi.latitude)),
]
for feature in features:
stream.send(feature)The stream.send function is used to post response messages.
def record_route(stream):
"""record_route implements a client streaming handler
Args:
stream: the stream object
Returns:
a RouteSummary with a summary of the traversed points.
"""
points = []
for point in stream:
points.append(point)
return pb.RouteSummary(
point_count = len(points),
distance = 2,
elapsed_time = 10,
)The stream is an iterable that will call .RevcMsg until the stream has been closed by the client.
Alternatively, the function stream.recv can be used to get a single message, or None if there are no more messages.
The return value of the function should return an appropriately typed message.
def route_chat(stream):
"""route_chat implements a bidirectional streaming handler
Args:
stream: the stream object
Returns:
None
"""
notes = []
for note in stream:
notes.append(note)
stream.send(note)In this implementation the function broadcasts a reponse on every request.
The time module contains time-related functions. For details, see https://github.com/google/starlark-go/blob/master/lib/time/time.go.
The os module contains functions for interacting with the operating system.
os.getenv("NAME")returns the value of the environment variableNAMEorNoneif not set.
See https://github.com/stackb/grpc-starlark/tree/master/cmd/grpcstar/testdata for details.
The thread module can be used to interact with the interpreter threading model.
thread.sleep(duration)pauses the current thread.thread.defer(fn, delay, count)runs another function in a new thread after the given delay. An optionalcountargument will repeat the callback invocation. This function is akin to the javascript functionssetTimoutandsetInterval.thread.namereturns the name of the current thread.
Example:
thread.defer(lambda: server.start(listener))`The net module contains network-related functions.
net.Listenerconstructs a new listener via the net.Listen func.
The process module contains subprocess-related functions.
process.runruns a subprocess.
