Skip to content

Commit 50e0ae1

Browse files
committed
Refactor OIDC API Proxy to implement proxy response handling with httpx, including synchronous and streaming responses
1 parent f01bad4 commit 50e0ae1

File tree

2 files changed

+63
-48
lines changed

2 files changed

+63
-48
lines changed

endpoints/helpers/endpoint.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,57 @@
11
import json
2+
from typing import Any
23

3-
from werkzeug import Response
4+
import httpx
5+
import werkzeug
46

57

6-
def OidcApiProxyErrorResponse(message: str, error_code: int = 500) -> Response:
7-
return Response(
8+
def proxy_response(
9+
method: str,
10+
url: str,
11+
headers: httpx._types.HeaderTypes | None,
12+
params: httpx._types.QueryParamTypes | None,
13+
json: Any | None,
14+
) -> werkzeug.Response:
15+
with httpx.Client() as client:
16+
response = client.request(method=method, url=url, headers=headers, params=params, json=json)
17+
18+
return werkzeug.Response(
19+
response=response.content,
20+
status=response.status_code,
21+
content_type=response.headers.get("Content-Type"),
22+
)
23+
24+
25+
def proxy_stream_response(
26+
method: str,
27+
url: str,
28+
headers: httpx._types.HeaderTypes | None,
29+
params: httpx._types.QueryParamTypes | None,
30+
json: Any | None,
31+
) -> werkzeug.Response:
32+
stream_context = httpx.stream(method=method, url=url, headers=headers, json=json)
33+
34+
# Manually enter the context manager to get the response
35+
stream_response = stream_context.__enter__()
36+
37+
# Create a generator to stream the response
38+
def generate():
39+
try:
40+
for chunk in stream_response.iter_bytes():
41+
yield chunk
42+
finally:
43+
# Manually exit the context manager after the generator is exhausted
44+
stream_context.__exit__(None, None, None)
45+
46+
return werkzeug.Response(
47+
generate(),
48+
status=stream_response.status_code,
49+
content_type=stream_response.headers.get("Content-Type"),
50+
)
51+
52+
53+
def OidcApiProxyErrorResponse(message: str, error_code: int = 500) -> werkzeug.Response:
54+
return werkzeug.Response(
855
json.dumps({"error": message}),
956
status=error_code,
1057
content_type="application/json",

endpoints/oidc_api_proxy.py

Lines changed: 13 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from typing import Any, Mapping
1+
from typing import Mapping
22
from urllib.parse import urlparse
33

4-
import httpx
54
from dify_plugin import Endpoint
65
from werkzeug import Request, Response
76

8-
from endpoints.helpers.endpoint import OidcApiProxyErrorResponse
7+
from endpoints.helpers.endpoint import OidcApiProxyErrorResponse, proxy_response, proxy_stream_response
98
from endpoints.helpers.oidc import OpenIDConnectDiscoveryProvider
109

1110

@@ -18,6 +17,10 @@ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
1817
dify_api_url = str(settings.get("dify_api_url", ""))
1918
dify_api_key = str(settings.get("dify_api_key", ""))
2019

20+
# prepare dify api url by removing trailing slash
21+
if dify_api_url.endswith("/"):
22+
dify_api_url = dify_api_url[:-1]
23+
2124
# Validate settings
2225
if not oidc_issuer:
2326
return OidcApiProxyErrorResponse("OpenID Connect Issuer is required", 503)
@@ -48,57 +51,22 @@ def _invoke(self, r: Request, values: Mapping, settings: Mapping) -> Response:
4851
## Prepare request
4952
##
5053

51-
# Replace URL with Dify API URL
52-
if dify_api_url.endswith("/"):
53-
dify_api_url = dify_api_url[:-1]
54-
55-
url = f"{dify_api_url}{r.full_path}"
54+
# prepare url
55+
url = f"{dify_api_url}{r.path}"
5656

57-
# Replace werkzeug headers to httpx headers,
58-
# with replacing host and authorization headers
57+
# prepare headers
5958
headers = {
6059
"Host": urlparse(dify_api_url).netloc,
6160
"Authorization": f"Bearer {dify_api_key}",
62-
"Content-Type": r.content_type or None,
61+
**({"Content-Type": r.headers["Content-Type"]} if r.headers.get("Content-Type") else {}),
6362
}
63+
64+
# prepare json if request is json
6465
json = r.get_json() if r.is_json else None
6566

6667
# Forward request to Dify API with Syncronous HTTP Client
6768
try:
68-
return proxy_stream_response(r, url, headers, json)
69+
return proxy_stream_response(method=r.method, url=url, headers=headers, params=r.args, json=json)
6970
except Exception as e:
7071
print(str(e))
7172
return OidcApiProxyErrorResponse(str(e), 500)
72-
73-
74-
def proxy_response(r: Request, url: str, headers: Mapping, json: Any | None) -> Response:
75-
with httpx.Client() as client:
76-
response = client.request(method=r.method, url=url, headers=headers, json=json)
77-
78-
return Response(
79-
response=response.content,
80-
status=response.status_code,
81-
content_type=response.headers.get("Content-Type"),
82-
)
83-
84-
85-
def proxy_stream_response(r: Request, url: str, headers: Mapping, json: Any | None) -> Response:
86-
stream_context = httpx.stream(method=r.method, url=url, headers=headers, json=json)
87-
88-
# Manually enter the context manager to get the response
89-
stream_response = stream_context.__enter__()
90-
91-
# Create a generator to stream the response
92-
def generate():
93-
try:
94-
for chunk in stream_response.iter_bytes():
95-
yield chunk
96-
finally:
97-
# Manually exit the context manager after the generator is exhausted
98-
stream_context.__exit__(None, None, None)
99-
100-
return Response(
101-
generate(),
102-
status=stream_response.status_code,
103-
content_type=stream_response.headers.get("Content-Type"),
104-
)

0 commit comments

Comments
 (0)