Skip to content

Commit

Permalink
add simple plain/text http invoker support
Browse files Browse the repository at this point in the history
  • Loading branch information
matthewmcnew committed Oct 1, 2018
1 parent a87dcee commit 10d9b72
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 2 deletions.
7 changes: 6 additions & 1 deletion invoker/function_invoker.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from shutil import copyfile

import grpc_server
import http_server


def invoke_function(func,interaction_model, env):
Expand All @@ -36,7 +37,11 @@ def invoke_function(func,interaction_model, env):
:param env: a dict containing the runtime environment, usually os.environ
:return: None
"""
grpc_server.run(func, interaction_model, env.get("GRPC_PORT", 10382))

if env.get("GRPC_PORT") is not None:
grpc_server.run(func, interaction_model, env.get("GRPC_PORT", 10382))
else:
http_server.run(func=func, port=int(env.get("HTTP_PORT", 8080)))

This comment has been minimized.

Copy link
@dturanski

dturanski Oct 3, 2018

Knative doesn't use GRCP_PORT and HTTP_PORT env vars. There is a PORT referenced. See knative/docs#221 . Re Grpc, the advice I've gotten from the riff team is it doesn't matter if we start a Grpc server since Knative only supports HTTP. We can choose to start it in addition to the http server, but I'm inclined to disable it for now. So I would remove 41-43 and make 44 http_server.run(func=func, port=int(env.get("PORT", 8080))) Make sense?

This comment has been minimized.

Copy link
@matthewmcnew

matthewmcnew Oct 7, 2018

Author Owner

@dturanski That makes sense. Would it make sense to remove GRCP completely?

This comment has been minimized.

Copy link
@dturanski

dturanski Oct 9, 2018

I'm ok wit removing gRPC completely. It's not clear when knative will support it and when it does we can restore this code. knative/serving#813

This comment has been minimized.

Copy link
@matthewmcnew

matthewmcnew Oct 9, 2018

Author Owner

That makes sense. I left it in vestigially for change simplicity. However, it should be uninvolved to remove it and the corresponding tests as well.



def install_function(env):
Expand Down
12 changes: 12 additions & 0 deletions invoker/http_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def run(func, port):

from flask import Flask, request

app = Flask("app")

@app.route("/", methods=['POST', 'GET'])
def hello():

This comment has been minimized.

Copy link
@dturanski

dturanski Oct 3, 2018

change hello() to invoke()

arg = request.data.decode("utf-8")
return func(arg)

app.run(port=port)
3 changes: 2 additions & 1 deletion invoker/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
protobuf
grpcio
grpcio
flask
75 changes: 75 additions & 0 deletions tests/test_http_functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import sys

from tests.utils import testutils

import unittest
import subprocess
import os
import signal
import urllib.request

sys.path.append('invoker')

PYTHON = sys.executable


class HttpFunctionTest(unittest.TestCase):
"""
Spawns function_invoker in a separate process.
Assumes os.getcwd() is the project base directory
"""

@classmethod
def setUpClass(cls):
cls.workingdir = os.path.abspath("./invoker")
cls.command = "%s function_invoker.py" % PYTHON
cls.PYTHONPATH = '%s:%s' % ('%s/tests/functions:$PYTHONPATH' % os.getcwd(), '%s/invoker' % os.getcwd())

def tearDown(self):
os.killpg(os.getpgid(self.process.pid), signal.SIGTERM)

def test_upper(self):
port = testutils.find_free_port()

env = {
'PYTHONPATH': self.PYTHONPATH,
'HTTP_PORT': str(port),
'FUNCTION_URI': 'file://%s/tests/functions/upper.py?handler=handle' % os.getcwd()
}

self.process = subprocess.Popen(self.command,
cwd=self.workingdir,
shell=True,
env=env,
preexec_fn=os.setsid,
)

def generate_messages():
messages = [
("hello", 'UTF-8'),
("world", 'UTF-8'),
("foo", 'UTF-8'),
("bar", 'UTF-8'),
]
for msg in messages:
yield msg

responses = self.call_multiple_http_messages(port, generate_messages())
expected = [b'HELLO', b'WORLD', b'FOO', b'BAR']

for response in responses:
self.assertTrue(response in expected)

self.assertEqual(len(responses), len(expected))

def call_http(self, port, message):
url = 'http://localhost:' + str(port)

req = urllib.request.Request(url, message.encode(), method="POST", headers={"content-type": "text/plain"})
response = urllib.request.urlopen(req)
return response.read()

def call_multiple_http_messages(self, port, messages):
import time
time.sleep(1)
return [self.call_http(port, message[0]) for message in messages]

0 comments on commit 10d9b72

Please sign in to comment.