/* * Copyright 2000-2017 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.io; import com.intellij.openapi.diagnostic.Logger; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpRequestDecoder; import io.netty.handler.codec.http.HttpResponseEncoder; import io.netty.handler.codec.http.cors.CorsConfig; import io.netty.handler.codec.http.cors.CorsConfigBuilder; import io.netty.handler.codec.http.cors.CorsHandler; import io.netty.handler.stream.ChunkedWriteHandler; import io.netty.util.concurrent.GlobalEventExecutor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; import org.jetbrains.ide.PooledThreadExecutor; import java.io.IOException; import java.net.BindException; import java.net.ConnectException; import java.util.concurrent.TimeUnit; public final class NettyUtil { public static final int MAX_CONTENT_LENGTH = 100 * 1024 * 1024; public static final int DEFAULT_CONNECT_ATTEMPT_COUNT = 20; public static final int MIN_START_TIME = 100; public static void logAndClose(@NotNull Throwable error, @NotNull Logger log, @NotNull Channel channel) { // don't report about errors while connecting // WEB-7727 try { if (error instanceof ConnectException) { log.debug(error); } else { log(error, log); } } finally { log.info("Channel will be closed due to error"); channel.close(); } } public static void log(@NotNull Throwable throwable, @NotNull Logger log) { if (isAsWarning(throwable)) { log.warn(throwable); } else { log.error(throwable); } } private static boolean isAsWarning(@NotNull Throwable throwable) { String message = throwable.getMessage(); if (message == null) { return false; } return (throwable instanceof IOException && message.equals("An existing connection was forcibly closed by the remote host")) || (throwable instanceof ChannelException && message.startsWith("Failed to bind to: ")) || throwable instanceof BindException || (message.startsWith("Connection reset") || message.equals("Operation timed out") || message.equals("Connection timed out")); } public static Bootstrap nioClientBootstrap() { return nioClientBootstrap(new NioEventLoopGroup(1, PooledThreadExecutor.INSTANCE)); } public static Bootstrap nioClientBootstrap(@NotNull EventLoopGroup eventLoopGroup) { Bootstrap bootstrap = new Bootstrap().group(eventLoopGroup).channel(NioSocketChannel.class); bootstrap.option(ChannelOption.TCP_NODELAY, true).option(ChannelOption.SO_KEEPALIVE, true); return bootstrap; } public static void addHttpServerCodec(@NotNull ChannelPipeline pipeline) { pipeline.addLast("httpRequestEncoder", new HttpResponseEncoder()); // https://jetbrains.zendesk.com/agent/tickets/68315 pipeline.addLast("httpRequestDecoder", new HttpRequestDecoder(16 * 1024, 16 * 1024, 8192)); pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(MAX_CONTENT_LENGTH)); // could be added earlier if HTTPS if (pipeline.get(ChunkedWriteHandler.class) == null) { pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler()); } pipeline.addLast("corsHandler", new CorsHandlerDoNotUseOwnLogger(CorsConfigBuilder .forAnyOrigin() .shortCircuit() .allowCredentials() .allowNullOrigin() .allowedRequestMethods(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.PATCH) .allowedRequestHeaders("origin", "accept", "authorization", "content-type", "x-ijt") .build())); } private static final class CorsHandlerDoNotUseOwnLogger extends CorsHandler { public CorsHandlerDoNotUseOwnLogger(@NotNull CorsConfig config) { super(config); } @Override public void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception { context.fireExceptionCaught(cause); } } @TestOnly public static void awaitQuiescenceOfGlobalEventExecutor(long timeout, @NotNull TimeUnit unit) { try { @NotNull GlobalEventExecutor executor = GlobalEventExecutor.INSTANCE; executor.awaitInactivity(timeout, unit); } catch (InterruptedException ignored) { } catch (IllegalStateException ignored) { // thread did not start } } }