Skip to content

Commit 319d8f9

Browse files
committed
OPTIONS Handler and specific CORS processor
1 parent d12a764 commit 319d8f9

39 files changed

Lines changed: 1030 additions & 595 deletions

src/main/java/act/Act.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import act.event.EventBus;
1919
import act.handler.builtin.controller.*;
2020
import act.handler.builtin.controller.impl.ReflectedHandlerInvoker;
21+
import act.inject.DependencyInjector;
2122
import act.job.AppJobManager;
2223
import act.metric.MetricPlugin;
2324
import act.metric.SimpleMetricPlugin;
@@ -399,6 +400,15 @@ public static AppJobManager jobManager() {
399400
return App.instance().jobManager();
400401
}
401402

403+
/**
404+
* Returns the {@link App app}'s {@link DependencyInjector}
405+
* @param <DI> the generic type of injector
406+
* @return the app's injector
407+
*/
408+
public static <DI extends DependencyInjector> DI injector() {
409+
return App.instance().injector();
410+
}
411+
402412
/**
403413
* Return an instance with give class name
404414
* @param className the class name

src/main/java/act/app/ActionContext.java

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,17 @@
22

33
import act.Act;
44
import act.Destroyable;
5+
import act.conf.AppConfig;
56
import act.data.MapUtil;
67
import act.data.RequestBodyParser;
78
import act.event.ActEvent;
9+
import act.event.ActEventListenerBase;
810
import act.event.EventBus;
11+
import act.event.OnceEventListenerBase;
912
import act.handler.RequestHandler;
13+
import act.handler.event.BeforeCommit;
1014
import act.route.Router;
1115
import act.util.ActContext;
12-
import com.alibaba.fastjson.JSON;
13-
import com.alibaba.fastjson.JSONArray;
14-
import com.alibaba.fastjson.JSONObject;
1516
import org.osgl.$;
1617
import org.osgl.concurrent.ContextLocal;
1718
import org.osgl.http.H;
@@ -20,14 +21,15 @@
2021
import org.osgl.util.C;
2122
import org.osgl.util.E;
2223
import org.osgl.util.S;
23-
import org.osgl.util.Str;
2424
import org.osgl.web.util.UserAgent;
2525

2626
import javax.enterprise.context.RequestScoped;
2727
import javax.inject.Inject;
2828
import javax.validation.ConstraintViolation;
2929
import java.util.*;
3030

31+
import static org.osgl.http.H.Header.Names.*;
32+
3133
/**
3234
* {@code AppContext} encapsulate contextual properties needed by
3335
* an application session
@@ -58,6 +60,7 @@ public class ActionContext extends ActContext.Base<ActionContext> implements Act
5860
private Router router;
5961
private RequestHandler handler;
6062
private UserAgent ua;
63+
private boolean disableCors;
6164

6265
@Inject
6366
private ActionContext(App app, H.Request request, H.Response response) {
@@ -68,6 +71,13 @@ private ActionContext(App app, H.Request request, H.Response response) {
6871
this._init();
6972
this.state = State.CREATED;
7073
this.saveLocal();
74+
app.eventBus().once(BeforeCommit.class, new OnceEventListenerBase() {
75+
@Override
76+
public boolean tryHandle(EventObject event) throws Exception {
77+
applyGlobalCorsSetting();
78+
return true;
79+
}
80+
});
7181
}
7282

7383
public State state() {
@@ -265,6 +275,28 @@ public ActionContext addUpload(String name, ISObject sobj) {
265275
return this;
266276
}
267277

278+
public void disableCORS() {
279+
this.disableCors = true;
280+
}
281+
282+
public void applyGlobalCorsSetting() {
283+
if (this.disableCors) {
284+
return;
285+
}
286+
AppConfig conf = config();
287+
if (!conf.corsEnabled()) {
288+
return;
289+
}
290+
H.Response r = resp();
291+
r.addHeaderIfNotAdded(ACCESS_CONTROL_ALLOW_ORIGIN, conf.corsAllowOrigin());
292+
if (request.method() == H.Method.OPTIONS) {
293+
r.addHeaderIfNotAdded(ACCESS_CONTROL_ALLOW_HEADERS, conf.corsAllowHeaders());
294+
r.addHeaderIfNotAdded(ACCESS_CONTROL_ALLOW_METHODS, conf.corsAllowMethods());
295+
r.addHeaderIfNotAdded(ACCESS_CONTROL_EXPOSE_HEADERS, conf.corsExposeHeaders());
296+
r.addHeaderIfNotAdded(ACCESS_CONTROL_MAX_AGE, S.string(conf.corsMaxAge()));
297+
}
298+
}
299+
268300
/**
269301
* Called by bytecode enhancer to set the name list of the render arguments that is update
270302
* by the enhancer

src/main/java/act/app/conf/AppConfigurator.java

Lines changed: 97 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package act.app.conf;
22

3+
import act.app.event.AppEventId;
34
import act.conf.AppConfig;
45
import act.route.RouteSource;
56
import act.route.Router;
@@ -10,8 +11,7 @@
1011

1112
import java.lang.reflect.Method;
1213
import java.lang.reflect.Modifier;
13-
import java.util.Map;
14-
import java.util.Set;
14+
import java.util.*;
1515

1616
/**
1717
* Base class for app developer implement source code based configuration
@@ -62,6 +62,10 @@ protected T registerStringValueResolver(Class<T> targetType, StringValueResolver
6262
return me();
6363
}
6464

65+
protected CorsSetting cors() {
66+
return new CorsSetting(this);
67+
}
68+
6569
protected RouteBuilder route(String path) {
6670
return new RouteBuilder(this).map(path);
6771
}
@@ -98,14 +102,104 @@ public <V> V propVal(String key) {
98102

99103
protected void releaseAppConfigResources() {}
100104

105+
protected static class CorsSetting {
106+
private AppConfigurator conf;
107+
private boolean enabled;
108+
private String allowOrigin;
109+
private int maxAge;
110+
private List<String> methods = new ArrayList<>();
111+
private List<String> headersBoth = new ArrayList<>();
112+
private List<String> headersAllowed = new ArrayList<>();
113+
private List<String> headersExpose = new ArrayList<>();
114+
115+
CorsSetting(AppConfigurator conf) {
116+
this.conf = conf;
117+
this.enabled = true;
118+
conf.app().jobManager().on(AppEventId.CONFIG_PREMERGE, new Runnable() {
119+
@Override
120+
public void run() {
121+
checkAndCommit();
122+
}
123+
});
124+
}
125+
126+
public CorsSetting enable() {
127+
enabled = true;
128+
return this;
129+
}
130+
131+
public CorsSetting disable() {
132+
enabled = false;
133+
return this;
134+
}
135+
136+
public CorsSetting allowOrigin(String allowOrigin) {
137+
E.illegalArgumentIf(S.blank(allowOrigin), "allow origin cannot be empty");
138+
this.allowOrigin = allowOrigin;
139+
return this;
140+
}
141+
142+
public CorsSetting allowMethods(String ... methods) {
143+
this.methods.addAll(C.listOf(methods));
144+
return this;
145+
}
146+
147+
public CorsSetting maxAge(int maxAge) {
148+
E.illegalArgumentIf(maxAge < 0);
149+
this.maxAge = maxAge;
150+
return this;
151+
}
152+
153+
public CorsSetting allowHeaders(String ... headers) {
154+
headersAllowed.addAll(C.listOf(headers));
155+
return this;
156+
}
157+
158+
public CorsSetting exposeHeaders(String ... headers) {
159+
headersExpose.addAll(C.listOf(headers));
160+
return this;
161+
}
162+
163+
public CorsSetting allowAndExposeHeaders(String ... headers) {
164+
headersBoth.addAll(C.listOf(headers));
165+
return this;
166+
}
167+
168+
private void checkAndCommit() {
169+
if (!enabled) {
170+
logger.info("Global CORS is disabled");
171+
conf.enableCors(false);
172+
return;
173+
}
174+
logger.info("Global CORS is enabled");
175+
conf.enableCors(true);
176+
conf.corsAllowOrigin(allowOrigin);
177+
conf.corsHeaders(consolidate(headersBoth));
178+
conf.corsAllowHeaders(consolidate(headersAllowed));
179+
conf.corsHeadersExpose(consolidate(headersExpose));
180+
conf.corsAllowMethods(consolidate(methods));
181+
conf.corsMaxAge(maxAge);
182+
}
183+
184+
private String consolidate(List<String> stringList) {
185+
if (stringList.isEmpty()) {
186+
return null;
187+
}
188+
Set<String> set = new HashSet<>();
189+
for (String s : stringList) {
190+
set.addAll(C.listOf(s.split(",")));
191+
}
192+
return S.join(", ", set);
193+
}
194+
}
195+
101196
protected static class RouteBuilder {
102197

103198
private AppConfigurator conf;
104199
private Router router;
105200
private C.List<H.Method> methods = C.newListOf(Router.supportedHttpMethods());
106201
private String path;
107202
private String action;
108-
private Class<?> controller;
109203
RouteBuilder(AppConfigurator config) {
110204
router = config.app().router();
111205
E.illegalStateIf(null == router);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package act.app.event;
2+
3+
import act.app.App;
4+
5+
/**
6+
* Emitted right before merging {@link act.app.conf.AppConfigurator app custom config}
7+
*/
8+
public class AppConfigPreMerge extends AppEvent {
9+
public AppConfigPreMerge(App source) {
10+
super(AppEventId.CONFIG_PREMERGE, source);
11+
}
12+
}

src/main/java/act/app/event/AppEventId.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ public AppEvent of(App app) {
1515
return new AppConfigLoaded(app);
1616
}
1717
},
18+
CONFIG_PREMERGE() {
19+
@Override
20+
public AppEvent of(App app) {
21+
return new AppConfigPreMerge(app);
22+
}
23+
},
1824
DB_SVC_LOADED () {
1925
@Override
2026
public AppEvent of(App app) {

src/main/java/act/boot/app/FullStackAppBootstrapClassLoader.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
import act.util.Jars;
1010
import org.osgl.$;
1111
import org.osgl.exception.UnexpectedException;
12-
import org.osgl.util.C;
13-
import org.osgl.util.E;
14-
import org.osgl.util.IO;
15-
import org.osgl.util.S;
12+
import org.osgl.util.*;
1613

1714
import java.io.File;
1815
import java.lang.reflect.Modifier;
@@ -37,6 +34,7 @@ public class FullStackAppBootstrapClassLoader extends BootstrapClassLoader imple
3734
private Map<String, byte[]> libBC = C.newMap();
3835
private List<Class<?>> actClasses = C.newList();
3936
private List<Class<?>> pluginClasses = new ArrayList<>();
37+
private String lineSeparator = OS.get().lineSeparator();
4038

4139
public FullStackAppBootstrapClassLoader(ClassLoader parent) {
4240
super(parent);
@@ -134,15 +132,15 @@ private void savePluginClasses() {
134132
}
135133
StringBuilder sb = S.builder();
136134
for (Class c : pluginClasses) {
137-
sb.append(c.getName()).append("\n");
135+
sb.append(c.getName()).append(lineSeparator);
138136
}
139137
sb.deleteCharAt(sb.length() - 1);
140138
saveToFile(".act.plugins", sb.toString());
141139
}
142140

143141
private void saveToFile(String name, String content) {
144142
StringBuilder sb = S.builder("#").append(jarsChecksum);
145-
sb.append("\n").append(content);
143+
sb.append(lineSeparator).append(content);
146144
File file = new File(name);
147145
IO.writeContent(sb.toString(), file);
148146
}
@@ -152,7 +150,7 @@ private void restoreClassInfoRegistry() {
152150
if (list.isEmpty()) {
153151
return;
154152
}
155-
String json = S.join("\n", list);
153+
String json = S.join(lineSeparator, list);
156154
classInfoRepository = ClassInfoRepository.parseJSON(json);
157155
}
158156

@@ -170,7 +168,7 @@ private List<String> restoreFromFile(String name) {
170168
File file = new File(name);
171169
if (file.canRead()) {
172170
String content = IO.readContentAsString(file);
173-
String[] sa = content.split("\n");
171+
String[] sa = content.split(lineSeparator);
174172
long fileChecksum = Long.parseLong(sa[0].substring(1));
175173
if (jarsChecksum.equals(fileChecksum)) {
176174
return C.listOf(sa).drop(1);

0 commit comments

Comments
 (0)