mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
RestService — base class of IDE REST service
DiffHttpService — diff service (/rest/diff) to compare n contents IDEA-133605 Implement true CORS in IDEA
This commit is contained in:
@@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.IdeActions;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Factory;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.SmartList;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -45,7 +46,7 @@ public abstract class DiffRequest {
|
||||
protected DiffRequest(@Nullable Project project) {
|
||||
myProject = project;
|
||||
myGenericData = new HashMap<String, Object>(2);
|
||||
myAdditional = new ArrayList<Pair<String, DiffRequest>>(0);
|
||||
myAdditional = new SmartList<Pair<String, DiffRequest>>();
|
||||
}
|
||||
|
||||
public void setToolbarAddons(@NotNull ToolbarAddons toolbarAddons) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -115,11 +115,6 @@ public class XmlRpcServerImpl implements XmlRpcServer {
|
||||
Responses.send(Responses.response("text/xml", result), context.channel(), request);
|
||||
return true;
|
||||
}
|
||||
else if (HttpMethod.POST.name().equals(request.headers().get("Access-Control-Request-Method"))) {
|
||||
LOG.assertTrue(request.method() == HttpMethod.OPTIONS);
|
||||
Responses.sendOptionsResponse("POST, OPTIONS", request, context);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.ide;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.intellij.ide.impl.ProjectUtil;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diff.DiffContent;
|
||||
import com.intellij.openapi.diff.DiffManager;
|
||||
import com.intellij.openapi.diff.DiffRequest;
|
||||
import com.intellij.openapi.diff.SimpleContent;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
import com.intellij.openapi.fileTypes.FileTypeRegistry;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.QueryStringDecoder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class DiffHttpService extends RestService {
|
||||
@Override
|
||||
protected boolean isMethodSupported(@NotNull HttpMethod method) {
|
||||
return method == HttpMethod.POST;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getServiceName() {
|
||||
return "diff";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
|
||||
final List<DiffContent> contents = new ArrayList<DiffContent>();
|
||||
final List<String> titles = new ArrayList<String>();
|
||||
|
||||
JsonReader reader = createJsonReader(request);
|
||||
if (reader.hasNext()) {
|
||||
String fileType = null;
|
||||
boolean reformat;
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
if (name.equals("fileTypeName")) {
|
||||
fileType = reader.nextString();
|
||||
}
|
||||
else if (name.equals("contents")) {
|
||||
String error = readContent(reader, contents, titles, fileType);
|
||||
if (error != null) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
else {
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
|
||||
if (contents.isEmpty()) {
|
||||
return "Empty request";
|
||||
}
|
||||
|
||||
final Project project = guessProject();
|
||||
if (project == null) {
|
||||
// Argument for @NotNull parameter 'project' of com/intellij/openapi/components/ServiceManager.getService must not be null
|
||||
return "No opened project, please open any project";
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
DiffManager.getInstance().getDiffTool().show(new DiffRequest(project) {
|
||||
@NotNull
|
||||
@Override
|
||||
public DiffContent[] getContents() {
|
||||
return contents.toArray(new DiffContent[contents.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getContentTitles() {
|
||||
return ArrayUtil.toStringArray(titles);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWindowTitle() {
|
||||
return "Diff Service";
|
||||
}
|
||||
});
|
||||
|
||||
ProjectUtil.focusProjectWindow(project, true);
|
||||
}
|
||||
}, project.getDisposed());
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String readContent(@NotNull JsonReader reader, @NotNull List<DiffContent> contents, @NotNull List<String> titles, @Nullable String defaultFileTypeName) throws IOException {
|
||||
FileTypeRegistry fileTypeRegistry = FileTypeRegistry.getInstance();
|
||||
|
||||
FileType defaultFileType = defaultFileTypeName == null ? null : fileTypeRegistry.findFileTypeByName(defaultFileTypeName);
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
String title = null;
|
||||
String fileType = null;
|
||||
String content = null;
|
||||
|
||||
reader.beginObject();
|
||||
while (reader.hasNext()) {
|
||||
String name = reader.nextName();
|
||||
if (name.equals("title")) {
|
||||
title = reader.nextString();
|
||||
}
|
||||
else if (name.equals("fileTypeName")) {
|
||||
fileType = reader.nextString();
|
||||
}
|
||||
else if (name.equals("content")) {
|
||||
content = reader.nextString();
|
||||
}
|
||||
else {
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
reader.endObject();
|
||||
|
||||
if (content == null) {;
|
||||
return "content is not specified";
|
||||
}
|
||||
|
||||
contents.add(new SimpleContent(content, fileType == null ? defaultFileType : fileTypeRegistry.findFileTypeByName(fileType)));
|
||||
titles.add(StringUtil.isEmptyOrSpaces(title) ? "" : title);
|
||||
}
|
||||
reader.endArray();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright 2000-2015 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.ide;
|
||||
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.MalformedJsonException;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectManager;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.openapi.wm.IdeFrame;
|
||||
import com.intellij.util.ExceptionUtil;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.io.Responses;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public abstract class RestService extends HttpRequestHandler {
|
||||
protected static final Logger LOG = Logger.getInstance(RestService.class);
|
||||
|
||||
@Override
|
||||
public final boolean isSupported(@NotNull FullHttpRequest request) {
|
||||
if (!isMethodSupported(request.method())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String prefix = "rest";
|
||||
String uri = request.uri();
|
||||
String serviceName = getServiceName();
|
||||
int minLength = 1 + prefix.length() + 1 + serviceName.length();
|
||||
if (uri.length() >= minLength &&
|
||||
uri.charAt(0) == '/' &&
|
||||
uri.regionMatches(true, 1, prefix, 0, prefix.length()) &&
|
||||
uri.regionMatches(true, 2 + prefix.length(), serviceName, 0, serviceName.length())) {
|
||||
if (uri.length() == minLength) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
char c = uri.charAt(minLength + 1);
|
||||
return c == '/' || c == '?';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract boolean isMethodSupported(@NotNull HttpMethod method);
|
||||
|
||||
@NotNull
|
||||
protected abstract String getServiceName();
|
||||
|
||||
@Override
|
||||
public final boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException {
|
||||
try {
|
||||
String error = execute(urlDecoder, request, context);
|
||||
if (error == null) {
|
||||
Responses.send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK), context.channel(), request);
|
||||
}
|
||||
else {
|
||||
Responses.sendStatus(HttpResponseStatus.BAD_REQUEST, context.channel(), error, request);
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
HttpResponseStatus status;
|
||||
// JsonReader exception
|
||||
//noinspection InstanceofCatchParameter
|
||||
if (e instanceof MalformedJsonException || (e instanceof IllegalStateException && e.getMessage().startsWith("Expected a "))) {
|
||||
LOG.warn(e);
|
||||
status = HttpResponseStatus.BAD_REQUEST;
|
||||
}
|
||||
else {
|
||||
LOG.error(e);
|
||||
status = HttpResponseStatus.INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
Responses.sendStatus(status, context.channel(), ExceptionUtil.getThrowableText(e), request);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable("error text or null if successful")
|
||||
public abstract String execute(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) throws IOException;
|
||||
|
||||
@NotNull
|
||||
protected static JsonReader createJsonReader(@NotNull FullHttpRequest request) {
|
||||
JsonReader reader = new JsonReader(new InputStreamReader(new ByteBufInputStream(request.content()), CharsetToolkit.UTF8_CHARSET));
|
||||
reader.setLenient(true);
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static Project guessProject() {
|
||||
IdeFrame lastFocusedFrame = IdeFocusManager.getGlobalInstance().getLastFocusedFrame();
|
||||
Project project = lastFocusedFrame == null ? null : lastFocusedFrame.getProject();
|
||||
if (project == null) {
|
||||
Project[] openProjects = ProjectManager.getInstance().getOpenProjects();
|
||||
return openProjects.length > 0 ? openProjects[0] : null;
|
||||
}
|
||||
return project;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.channel.socket.oio.OioSocketChannel;
|
||||
import io.netty.handler.codec.http.HttpObjectAggregator;
|
||||
import io.netty.handler.codec.http.HttpServerCodec;
|
||||
import io.netty.handler.codec.http.cors.CorsConfig;
|
||||
import io.netty.handler.codec.http.cors.CorsHandler;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.ide.PooledThreadExecutor;
|
||||
@@ -174,6 +176,6 @@ public final class NettyUtil {
|
||||
}
|
||||
|
||||
public static void addHttpServerCodec(ChannelPipeline pipeline) {
|
||||
pipeline.addLast(new HttpServerCodec(), new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||
pipeline.addLast(new HttpServerCodec(), new HttpObjectAggregator(MAX_CONTENT_LENGTH), new CorsHandler(CorsConfig.withAnyOrigin().build()));
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
@@ -63,10 +63,6 @@ public final class Responses {
|
||||
return response;
|
||||
}
|
||||
|
||||
public static void addAllowAnyOrigin(HttpResponse response) {
|
||||
response.headers().add(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
||||
}
|
||||
|
||||
public static void addDate(HttpResponse response) {
|
||||
if (!response.headers().contains(HttpHeaders.Names.DATE)) {
|
||||
addDate(response, Calendar.getInstance().getTime());
|
||||
@@ -99,7 +95,7 @@ public final class Responses {
|
||||
}
|
||||
}
|
||||
|
||||
public static void send(HttpResponse response, Channel channel, @Nullable HttpRequest request) {
|
||||
public static void send(@NotNull HttpResponse response, Channel channel, @Nullable HttpRequest request) {
|
||||
if (response.status() != HttpResponseStatus.NOT_MODIFIED && !HttpHeaders.isContentLengthSet(response)) {
|
||||
HttpHeaders.setContentLength(response,
|
||||
response instanceof FullHttpResponse ? ((FullHttpResponse)response).content().readableBytes() : 0);
|
||||
@@ -120,7 +116,6 @@ public final class Responses {
|
||||
public static void addCommonHeaders(HttpResponse response) {
|
||||
addServer(response);
|
||||
addDate(response);
|
||||
addAllowAnyOrigin(response);
|
||||
}
|
||||
|
||||
public static void send(CharSequence content, Channel channel, @Nullable HttpRequest request) {
|
||||
@@ -131,7 +126,7 @@ public final class Responses {
|
||||
send(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.copiedBuffer(content, charset)), channel, request);
|
||||
}
|
||||
|
||||
private static void send(HttpResponse response, Channel channel, boolean close) {
|
||||
private static void send(@NotNull HttpResponse response, @NotNull Channel channel, boolean close) {
|
||||
if (!channel.isActive()) {
|
||||
return;
|
||||
}
|
||||
@@ -154,7 +149,7 @@ public final class Responses {
|
||||
sendStatus(responseStatus, channel, null, request);
|
||||
}
|
||||
|
||||
public static void sendStatus(HttpResponseStatus responseStatus, Channel channel, @Nullable String description, @Nullable HttpRequest request) {
|
||||
public static void sendStatus(@NotNull HttpResponseStatus responseStatus, Channel channel, @Nullable String description, @Nullable HttpRequest request) {
|
||||
send(createStatusResponse(responseStatus, request, description), channel, request);
|
||||
}
|
||||
|
||||
@@ -175,11 +170,4 @@ public final class Responses {
|
||||
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html");
|
||||
return response;
|
||||
}
|
||||
|
||||
public static void sendOptionsResponse(String allowHeaders, HttpRequest request, ChannelHandlerContext context) {
|
||||
HttpResponse response = response(HttpResponseStatus.OK);
|
||||
response.headers().set(HttpHeaders.Names.ACCESS_CONTROL_ALLOW_METHODS, allowHeaders);
|
||||
response.headers().set(HttpHeaders.Names.ALLOW, allowHeaders);
|
||||
send(response, context.channel(), request);
|
||||
}
|
||||
}
|
||||
@@ -339,6 +339,8 @@
|
||||
<httpRequestHandler implementation="com.intellij.platform.ProjectSetRequestHandler"/>
|
||||
<postStartupActivity implementation="org.jetbrains.ide.BuiltInServerManagerImpl$MyPostStartupActivity"/>
|
||||
|
||||
<httpRequestHandler implementation="org.jetbrains.ide.DiffHttpService"/>
|
||||
|
||||
<editorNotificationProvider implementation="com.intellij.openapi.editor.impl.ForcedSoftWrapsNotificationProvider"/>
|
||||
<editorNotificationProvider implementation="com.intellij.ide.FileChangedNotificationProvider"/>
|
||||
<editorNotificationProvider implementation="com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginAdvertiserEditorNotificationProvider"/>
|
||||
|
||||
@@ -42,7 +42,8 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import static org.jetbrains.io.Responses.*;
|
||||
import static org.jetbrains.io.Responses.addKeepAliveIfNeed;
|
||||
import static org.jetbrains.io.Responses.sendStatus;
|
||||
|
||||
public final class BuiltInWebServer extends HttpRequestHandler {
|
||||
static final Logger LOG = Logger.getInstance(BuiltInWebServer.class);
|
||||
@@ -77,16 +78,11 @@ public final class BuiltInWebServer extends HttpRequestHandler {
|
||||
|
||||
@Override
|
||||
public boolean isSupported(@NotNull FullHttpRequest request) {
|
||||
return super.isSupported(request) || request.method() == HttpMethod.POST || request.method() == HttpMethod.OPTIONS;
|
||||
return super.isSupported(request) || request.method() == HttpMethod.POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(@NotNull QueryStringDecoder urlDecoder, @NotNull FullHttpRequest request, @NotNull ChannelHandlerContext context) {
|
||||
if (request.method() == HttpMethod.OPTIONS) {
|
||||
sendOptionsResponse("GET, POST, HEAD, OPTIONS", request, context);
|
||||
return true;
|
||||
}
|
||||
|
||||
String host = HttpHeaders.getHost(request);
|
||||
if (StringUtil.isEmpty(host)) {
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user