Serving Flow

    Server

    Client

    1. from jina import Client, Document
    2. c = Client(protocol='grpc', port=12345)
    3. c.post('/', Document())

    A Flow is a service by nature. Though implicitly, you are already using it as a service.

    When you start a Flow and call .post() inside the context, a jina.Client object is created and used for communication.

    Many times we need to use Flow & Client in a more explicit way, often due to one of the following reasons:

    • Flow and Client are on different machines: one on GPU, one on CPU;

    • Flow and Client have different lifetime: one lives longer, one lives shorter;

    • Multiple Clients want to access one Flow;

    • One Client want to interleave its access to multiple Flow;

    • Client is browser/curl/Postman.

    Before this cookbook, you are mostly using Flow as an implicit service. In the sequel, we will show you how to serve Flow in an explicit C/S style.

    Jina supports the three communication protocols grpc, websocket, and http between Flow and Client.

    ../../../_images/client-server.svg

    On the server-side, create an empty Flow and use .block to prevent the process from exiting.

    1. from jina import Flow
    2. with Flow(port_expose=12345) as f:
    3. f.block()
    1. [email protected][L]:ready and listening
    2. [email protected][I]:🎉 Flow is ready to use!
    3. 🔗 Protocol: GRPC
    4. 🏠 Local access: 0.0.0.0:12345
    5. 🔒 Private network: 192.168.1.15:12345
    6. 🌐 Public address: 197.26.36.43:12345

    Note that the host address is 192.168.1.15 and port_expose is 12345.

    While keeping this server open, let’s create a client on a different machine:

    1. from jina import Client
    2. c = Client(host='192.168.1.15', port=12345)
    3. c.post('/')
    1. [email protected][S]:connected to the gateway at 0.0.0.0:12345!

    Warning

    Multiple gRPC Client cannot be spawned using Threads because of an upstream issue. Use multiprocessing instead.

    via WebSocket

    1. [email protected][L]:ready and listening
    2. [email protected][I]:🎉 Flow is ready to use!
    3. 🔗 Protocol: WEBSOCKET
    4. 🏠 Local access: 0.0.0.0:12345
    5. 🔒 Private network: 192.168.1.15:12345
    6. 🌐 Public address: 197.26.36.43:12345

    This will serve the Flow with WebSocket, so any Client connecting to it should follow the WebSocket protocol as well.

    1. c.post('/')
    1. [email protected][S]:connected to the gateway at 0.0.0.0:12345!

    To enable a Flow to receive HTTP requests, you can add protocol='http' in the Flow constructor.

    1. from jina import Flow
    2. f = Flow(protocol='http', port_expose=12345)
    3. with f:
    4. f.block()
    1. [email protected][L]:ready and listening
    2. [email protected][I]:🎉 Flow is ready to use!
    3. 🔗 Protocol: HTTP
    4. 🏠 Local access: 0.0.0.0:12345
    5. 🔒 Private network: 192.168.1.15:12345
    6. 🌐 Public address: 197.26.36.43:12345
    7. 💬 Swagger UI: http://localhost:12345/docs
    8. 📚 Redoc: http://localhost:12345/redoc

    Enable cross-origin-resources-sharing (CORS)

    CORS is by default disabled for security. That means you can not access the service from a webpage with a different domain. To override this, simply do:

    You can navigate to the Swagger docs UI via http://localhost:12345/docs:

    Use curl to send HTTP request

    Now you can send data request via curl/Postman:

    1. $ curl --request POST 'http://localhost:12345/post' --header 'Content-Type: application/json' -d '{"data": [{"text": "hello world"}],"execEndpoint": "/index"}'
    2. {
    3. "header":{
    4. "requestId":"3a10f4a3711b441982ea17a162dec176",
    5. "status":null,
    6. "execEndpoint":"/index"
    7. },
    8. "parameters":null,
    9. "routes":[
    10. {
    11. "executor":"gateway",
    12. "startTime":"2022-01-26T10:40:43.988564+00:00",
    13. "endTime":"2022-01-26T10:40:43.989243+00:00",
    14. "status":null
    15. }
    16. ],
    17. "data":[
    18. {
    19. "id":"69f99b5a7e9411ec91f1e86a64801cb1",
    20. "parent_id":null,
    21. "granularity":null,
    22. "adjacency":null,
    23. "blob":null,
    24. "tensor":null,
    25. "mime_type":"text/plain",
    26. "text":"hello world",
    27. "weight":null,
    28. "uri":null,
    29. "tags":null,
    30. "offset":null,
    31. "location":null,
    32. "modality":null,
    33. "evaluations":null,
    34. "scores":null,
    35. "chunks":null,
    36. "matches":null
    37. ]
    38. }

    One can also use Python Client to send HTTP request, simply:

    1. from jina import Client
    2. c = Client(protocol='http', port=12345)
    3. c.post('/', ...)

    This HTTP client is less-performant on large data, it does not stream. Hence, it should be only used for debugging & testing.

    Extend HTTP Interface

    By default the following endpoints are exposed to the public:

    EndpointDescription
    /statusCheck Jina service running status
    /postCorresponds to f.post() method in Python
    /indexCorresponds to f.post(‘/index’) method in Python
    /searchCorresponds to f.post(‘/search’) method in Python
    /updateCorresponds to f.post(‘/update’) method in Python
    /deleteCorresponds to f.post(‘/delete’) method in Python

    Hide CRUD and debug endpoints from HTTP interface

    User can decide to hide CRUD and debug endpoints in production, or when the context is not applicable. For example, in the code snippet below, we didn’t implement any CRUD endpoints for the executor, hence it does not make sense to expose them to public.

    1. from jina import Flow
    2. f = Flow(protocol='http',
    3. no_debug_endpoints=True,
    4. no_crud_endpoints=True)

    ../../../_images/hide-crud-debug-endpoints.png

    Expose customized endpoints to HTTP interface

    Flow.expose_endpoint can be used to expose executor’s endpoint to HTTP interface, e.g.

    1. from jina import Executor, requests, Flow
    2. class MyExec(Executor):
    3. @requests(on='/foo')
    4. def foo(self, docs, **kwargs):
    5. pass
    6. f = Flow(protocol='http').add(uses=MyExec)
    7. f.expose_endpoint('/foo', summary='my endpoint')
    8. with f:
    9. f.block()

    ../../../_images/customized-foo-endpoint.png

    Now, sending HTTP data request to /foo is equivalent as calling f.post('/foo', ...) in Python.

    You can add more kwargs to build richer semantics on your HTTP endpoint. Those meta information will be rendered by Swagger UI and be forwarded to the OpenAPI schema.

    1. f.expose_endpoint('/bar',
    2. summary='my endpoint',
    3. tags=['fine-tuning'],
    4. methods=['PUT']
    5. )

    You can enable custom endpoints in a Flow using yaml syntax as well.

    If you want to add more customized routes, configs, options to HTTP interface, you can simply override jina.helper.extend_rest_interface function as follows:

    1. import jina.helper
    2. from jina import Flow
    3. def extend_rest_function(app):
    4. @app.get('/hello', tags=['My Extended APIs'])
    5. async def foo():
    6. return 'hello'
    7. return app
    8. jina.helper.extend_rest_interface = extend_rest_function
    9. f = Flow(protocol='http')
    10. f.block()

    And you will see /hello is now available: