Skip to content

Commit e61677e

Browse files
committed
[Stream] Support HttpServlet
1 parent c9b3fd1 commit e61677e

3 files changed

Lines changed: 199 additions & 0 deletions

File tree

pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
<guava.version>32.1.2-jre</guava.version>
5858
<retrofit2.version>2.9.0</retrofit2.version>
5959
<findbugs.version>3.0.1</findbugs.version>
60+
<servlet.version>6.0.0</servlet.version>
61+
<mockito.version>5.4.0</mockito.version>
6062
<plugin.maven.checkstyle.version>3.0.0</plugin.maven.checkstyle.version>
6163
<plugin.maven.findbugs.version>3.0.5</plugin.maven.findbugs.version>
6264
<plugin.maven.source.version>2.2.1</plugin.maven.source.version>
@@ -133,6 +135,18 @@
133135
<artifactId>findbugs-annotations</artifactId>
134136
<version>${findbugs.version}</version>
135137
</dependency>
138+
<dependency>
139+
<groupId>jakarta.servlet</groupId>
140+
<artifactId>jakarta.servlet-api</artifactId>
141+
<version>${servlet.version}</version>
142+
<scope>provided</scope>
143+
</dependency>
144+
<dependency>
145+
<groupId>org.mockito</groupId>
146+
<artifactId>mockito-core</artifactId>
147+
<version>${mockito.version}</version>
148+
<scope>test</scope>
149+
</dependency>
136150
</dependencies>
137151

138152
<build>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package org.devlive.sdk.openai.listener;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import jakarta.servlet.http.HttpSession;
6+
import lombok.Builder;
7+
import lombok.extern.slf4j.Slf4j;
8+
import okhttp3.Response;
9+
import okhttp3.sse.EventSource;
10+
import okhttp3.sse.EventSourceListener;
11+
import org.apache.commons.lang3.ObjectUtils;
12+
import org.devlive.sdk.openai.exception.ParamException;
13+
import org.devlive.sdk.openai.response.CompleteResponse;
14+
import org.devlive.sdk.openai.utils.JsonUtils;
15+
16+
import java.io.IOException;
17+
import java.time.LocalDateTime;
18+
19+
@Slf4j
20+
@Builder
21+
public class HttpServletEventSourceListener
22+
extends EventSourceListener
23+
{
24+
private HttpServletRequest request;
25+
private HttpServletResponse response;
26+
private JsonUtils<CompleteResponse> jsonUtils = JsonUtils.getInstance();
27+
28+
private HttpServletEventSourceListener(HttpServletEventSourceListenerBuilder builder)
29+
{
30+
if (ObjectUtils.isEmpty(builder.request)) {
31+
builder.request(null);
32+
}
33+
this.request = builder.request;
34+
35+
if (ObjectUtils.isEmpty(builder.response)) {
36+
builder.response(null);
37+
}
38+
this.response = builder.response;
39+
}
40+
41+
@Override
42+
public void onOpen(EventSource eventSource, Response response)
43+
{
44+
log.info("HttpServlet listener opened on time {}", LocalDateTime.now());
45+
46+
this.response.setContentType("text/event-stream");
47+
this.response.setCharacterEncoding("UTF-8");
48+
49+
HttpSession session = this.request.getSession();
50+
session.setAttribute("eventSourceListener", this);
51+
}
52+
53+
@Override
54+
public void onClosed(EventSource eventSource)
55+
{
56+
log.info("HttpServlet listener closed on time {}", LocalDateTime.now());
57+
this.close();
58+
}
59+
60+
@Override
61+
public void onEvent(EventSource eventSource, String id, String type, String data)
62+
{
63+
if (data.equals("[DONE]")) {
64+
eventSource.cancel();
65+
this.close();
66+
}
67+
else {
68+
try {
69+
CompleteResponse completeResponse = jsonUtils.getObject(data, CompleteResponse.class);
70+
log.debug("HttpServlet event received on time {} id {} type {} data {}", LocalDateTime.now(), id, type, completeResponse.getChoices().get(0).getContent());
71+
this.response.getWriter().write(completeResponse.getChoices().get(0).getContent());
72+
this.response.flushBuffer();
73+
}
74+
catch (IOException e) {
75+
log.warn("HttpServlet event error on time {} id {} type {} data {}", LocalDateTime.now(), id, type, data, e);
76+
}
77+
}
78+
}
79+
80+
@Override
81+
public void onFailure(EventSource eventSource, Throwable throwable, Response response)
82+
{
83+
if (ObjectUtils.isNotEmpty(throwable)) {
84+
if (throwable.getMessage().endsWith("CANCEL")) {
85+
log.info("HttpServlet listener cancelled on time {}", LocalDateTime.now());
86+
this.onClosed(eventSource);
87+
}
88+
else {
89+
log.error("HttpServlet listener throwable \n{}\n response: \n{}\n", throwable, response);
90+
}
91+
}
92+
else {
93+
log.error("HttpServlet listener failure with empty throwable. Response: \n{}\n", response);
94+
}
95+
eventSource.cancel();
96+
this.close();
97+
}
98+
99+
private void close()
100+
{
101+
HttpSession session = this.request.getSession();
102+
if (session != null) {
103+
session.removeAttribute("eventSourceListener");
104+
}
105+
}
106+
107+
public static class HttpServletEventSourceListenerBuilder
108+
{
109+
public HttpServletEventSourceListenerBuilder request(HttpServletRequest request)
110+
{
111+
if (ObjectUtils.isEmpty(request)) {
112+
throw new ParamException("Invalid request instance must not be null");
113+
}
114+
this.request = request;
115+
return this;
116+
}
117+
118+
public HttpServletEventSourceListenerBuilder response(HttpServletResponse response)
119+
{
120+
if (ObjectUtils.isEmpty(response)) {
121+
throw new ParamException("Invalid response instance must not be null");
122+
}
123+
this.response = response;
124+
return this;
125+
}
126+
127+
public HttpServletEventSourceListener build()
128+
{
129+
return new HttpServletEventSourceListener(this);
130+
}
131+
}
132+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package org.devlive.sdk.openai.listener;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import jakarta.servlet.http.HttpSession;
6+
import org.devlive.sdk.openai.OpenAiClient;
7+
import org.devlive.sdk.openai.entity.CompletionEntity;
8+
import org.junit.Test;
9+
import org.mockito.Mockito;
10+
11+
import java.io.IOException;
12+
import java.io.PrintWriter;
13+
14+
public class HttpServletEventSourceListenerTest
15+
{
16+
@Test
17+
public void test()
18+
throws IOException
19+
{
20+
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
21+
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
22+
Mockito.when(request.getMethod()).thenReturn("GET");
23+
24+
HttpSession session = Mockito.mock(HttpSession.class);
25+
Mockito.when(request.getSession()).thenReturn(session);
26+
27+
PrintWriter writer = Mockito.mock(PrintWriter.class);
28+
Mockito.when(response.getWriter()).thenReturn(writer);
29+
30+
HttpServletEventSourceListener listener = HttpServletEventSourceListener.builder()
31+
.request(request)
32+
.response(response)
33+
.build();
34+
35+
OpenAiClient openAiClient = OpenAiClient.builder()
36+
.apiKey(System.getProperty("openai.token"))
37+
.listener(listener)
38+
.build();
39+
40+
CompletionEntity configure = CompletionEntity.builder()
41+
.prompt("How to create a http servlet stream completion")
42+
.temperature(2D)
43+
.build();
44+
openAiClient.createCompletion(configure);
45+
46+
try {
47+
Thread.sleep(10000);
48+
}
49+
catch (InterruptedException e) {
50+
e.printStackTrace();
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)