Add support for HAProxyMessageEncoder (#10175)

Motivation:

Add support for HAProxyMessageEncoder.
This should help java based HAProxy server implementations propagate proxy information.

Modification:

Add public constructors for `HAProxyMessage`, `HAProxyTLV`, `HAProxySSLTLV`.
Add additional argument checks for `HAProxyMessage` and modify exceptions thrown when creating via public constructors directly.
Introduce a `@Sharable` `HAProxyMessageEncoder` which encodes a `HAProxyMessage` into a byte array.
Add an example `HAProxyServer` and `HAProxyClient` to `io.netty.example`

Result:

Fixes #10164
This commit is contained in:
jrhee17 2020-04-16 16:35:06 +09:00 committed by Norman Maurer
parent 9077acb6ab
commit 9ac005222b
12 changed files with 1053 additions and 51 deletions

View File

@ -56,5 +56,31 @@ final class HAProxyConstants {
static final byte TPAF_UNIX_STREAM_BYTE = 0x31; static final byte TPAF_UNIX_STREAM_BYTE = 0x31;
static final byte TPAF_UNIX_DGRAM_BYTE = 0x32; static final byte TPAF_UNIX_DGRAM_BYTE = 0x32;
/**
* V2 protocol binary header prefix
*/
static final byte[] BINARY_PREFIX = {
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x00,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x51,
(byte) 0x55,
(byte) 0x49,
(byte) 0x54,
(byte) 0x0A
};
static final byte[] TEXT_PREFIX = {
(byte) 'P',
(byte) 'R',
(byte) 'O',
(byte) 'X',
(byte) 'Y',
};
private HAProxyConstants() { } private HAProxyConstants() { }
} }

View File

@ -26,6 +26,7 @@ import io.netty.util.NetUtil;
import io.netty.util.ResourceLeakDetector; import io.netty.util.ResourceLeakDetector;
import io.netty.util.ResourceLeakDetectorFactory; import io.netty.util.ResourceLeakDetectorFactory;
import io.netty.util.ResourceLeakTracker; import io.netty.util.ResourceLeakTracker;
import io.netty.util.internal.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -60,9 +61,16 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
} }
/** /**
* Creates a new instance * Creates a new instance of HAProxyMessage.
* @param protocolVersion the protocol version.
* @param command the command.
* @param proxiedProtocol the protocol containing the address family and transport protocol.
* @param sourceAddress the source address.
* @param destinationAddress the destination address.
* @param sourcePort the source port. This value must be 0 for unix, unspec addresses.
* @param destinationPort the destination port. This value must be 0 for unix, unspec addresses.
*/ */
private HAProxyMessage( public HAProxyMessage(
HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) { String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) {
@ -71,19 +79,29 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
} }
/** /**
* Creates a new instance * Creates a new instance of HAProxyMessage.
* @param protocolVersion the protocol version.
* @param command the command.
* @param proxiedProtocol the protocol containing the address family and transport protocol.
* @param sourceAddress the source address.
* @param destinationAddress the destination address.
* @param sourcePort the source port. This value must be 0 for unix, unspec addresses.
* @param destinationPort the destination port. This value must be 0 for unix, unspec addresses.
* @param tlvs the list of tlvs.
*/ */
private HAProxyMessage( public HAProxyMessage(
HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
String sourceAddress, String destinationAddress, int sourcePort, int destinationPort, String sourceAddress, String destinationAddress, int sourcePort, int destinationPort,
List<HAProxyTLV> tlvs) { List<? extends HAProxyTLV> tlvs) {
requireNonNull(protocolVersion, "protocolVersion");
requireNonNull(proxiedProtocol, "proxiedProtocol"); requireNonNull(proxiedProtocol, "proxiedProtocol");
requireNonNull(tlvs, "tlvs");
AddressFamily addrFamily = proxiedProtocol.addressFamily(); AddressFamily addrFamily = proxiedProtocol.addressFamily();
checkAddress(sourceAddress, addrFamily); checkAddress(sourceAddress, addrFamily);
checkAddress(destinationAddress, addrFamily); checkAddress(destinationAddress, addrFamily);
checkPort(sourcePort); checkPort(sourcePort, addrFamily);
checkPort(destinationPort); checkPort(destinationPort, addrFamily);
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.command = command; this.command = command;
@ -329,9 +347,13 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)"); throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
} }
return new HAProxyMessage( try {
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, return new HAProxyMessage(
protAndFam, parts[2], parts[3], parts[4], parts[5]); HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
protAndFam, parts[2], parts[3], parts[4], parts[5]);
} catch (RuntimeException e) {
throw new HAProxyProtocolException("invalid HAProxy message", e);
}
} }
/** /**
@ -373,18 +395,18 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
* *
* @param value the port * @param value the port
* @return port as an integer * @return port as an integer
* @throws HAProxyProtocolException if port is not a valid integer * @throws IllegalArgumentException if port is not a valid integer
*/ */
private static int portStringToInt(String value) { private static int portStringToInt(String value) {
int port; int port;
try { try {
port = Integer.parseInt(value); port = Integer.parseInt(value);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
throw new HAProxyProtocolException("invalid port: " + value, e); throw new IllegalArgumentException("invalid port: " + value, e);
} }
if (port <= 0 || port > 65535) { if (port <= 0 || port > 65535) {
throw new HAProxyProtocolException("invalid port: " + value + " (expected: 1 ~ 65535)"); throw new IllegalArgumentException("invalid port: " + value + " (expected: 1 ~ 65535)");
} }
return port; return port;
@ -395,7 +417,7 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
* *
* @param address human-readable address * @param address human-readable address
* @param addrFamily the {@link AddressFamily} to check the address against * @param addrFamily the {@link AddressFamily} to check the address against
* @throws HAProxyProtocolException if the address is invalid * @throws IllegalArgumentException if the address is invalid
*/ */
private static void checkAddress(String address, AddressFamily addrFamily) { private static void checkAddress(String address, AddressFamily addrFamily) {
requireNonNull(addrFamily, "addrFamily"); requireNonNull(addrFamily, "addrFamily");
@ -403,10 +425,14 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
switch (addrFamily) { switch (addrFamily) {
case AF_UNSPEC: case AF_UNSPEC:
if (address != null) { if (address != null) {
throw new HAProxyProtocolException("unable to validate an AF_UNSPEC address: " + address); throw new IllegalArgumentException("unable to validate an AF_UNSPEC address: " + address);
} }
return; return;
case AF_UNIX: case AF_UNIX:
requireNonNull(address, "address");
if (address.getBytes(CharsetUtil.US_ASCII).length > 108) {
throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
}
return; return;
} }
@ -415,28 +441,41 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
switch (addrFamily) { switch (addrFamily) {
case AF_IPv4: case AF_IPv4:
if (!NetUtil.isValidIpV4Address(address)) { if (!NetUtil.isValidIpV4Address(address)) {
throw new HAProxyProtocolException("invalid IPv4 address: " + address); throw new IllegalArgumentException("invalid IPv4 address: " + address);
} }
break; break;
case AF_IPv6: case AF_IPv6:
if (!NetUtil.isValidIpV6Address(address)) { if (!NetUtil.isValidIpV6Address(address)) {
throw new HAProxyProtocolException("invalid IPv6 address: " + address); throw new IllegalArgumentException("invalid IPv6 address: " + address);
} }
break; break;
default: default:
throw new Error(); throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
} }
} }
/** /**
* Validate a UDP/TCP port * Validate the port depending on the addrFamily.
* *
* @param port the UDP/TCP port * @param port the UDP/TCP port
* @throws HAProxyProtocolException if the port is out of range (0-65535 inclusive) * @throws IllegalArgumentException if the port is out of range (0-65535 inclusive)
*/ */
private static void checkPort(int port) { private static void checkPort(int port, AddressFamily addrFamily) {
if (port < 0 || port > 65535) { switch (addrFamily) {
throw new HAProxyProtocolException("invalid port: " + port + " (expected: 1 ~ 65535)"); case AF_IPv6:
case AF_IPv4:
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("invalid port: " + port + " (expected: 0 ~ 65535)");
}
break;
case AF_UNIX:
case AF_UNSPEC:
if (port != 0) {
throw new IllegalArgumentException("port cannot be specified with addrFamily: " + addrFamily);
}
break;
default:
throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
} }
} }
@ -498,6 +537,14 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
return tlvs; return tlvs;
} }
int tlvNumBytes() {
int tlvNumBytes = 0;
for (int i = 0; i < tlvs.size(); i++) {
tlvNumBytes += tlvs.get(i).totalNumBytes();
}
return tlvNumBytes;
}
@Override @Override
public HAProxyMessage touch() { public HAProxyMessage touch() {
tryRecord(); tryRecord();
@ -556,4 +603,26 @@ public final class HAProxyMessage extends AbstractReferenceCounted {
} }
} }
} }
@Override
public String toString() {
StringBuilder sb = new StringBuilder(256)
.append(StringUtil.simpleClassName(this))
.append("(protocolVersion: ").append(protocolVersion)
.append(", command: ").append(command)
.append(", proxiedProtocol: ").append(proxiedProtocol)
.append(", sourceAddress: ").append(sourceAddress)
.append(", destinationAddress: ").append(destinationAddress)
.append(", sourcePort: ").append(sourcePort)
.append(", destinationPort: ").append(destinationPort)
.append(", tlvs: [");
if (!tlvs.isEmpty()) {
for (HAProxyTLV tlv: tlvs) {
sb.append(tlv).append(", ");
}
sb.setLength(sb.length() - 2);
}
sb.append("])");
return sb.toString();
}
} }

View File

@ -21,6 +21,9 @@ import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.ProtocolDetectionResult; import io.netty.handler.codec.ProtocolDetectionResult;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import static io.netty.handler.codec.haproxy.HAProxyConstants.*;
/** /**
* Decodes an HAProxy proxy protocol header * Decodes an HAProxy proxy protocol header
* *
@ -47,32 +50,6 @@ public class HAProxyMessageDecoder extends ByteToMessageDecoder {
*/ */
private static final int V2_MAX_TLV = 65535 - 216; private static final int V2_MAX_TLV = 65535 - 216;
/**
* Binary header prefix
*/
private static final byte[] BINARY_PREFIX = {
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x00,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x51,
(byte) 0x55,
(byte) 0x49,
(byte) 0x54,
(byte) 0x0A
};
private static final byte[] TEXT_PREFIX = {
(byte) 'P',
(byte) 'R',
(byte) 'O',
(byte) 'X',
(byte) 'Y',
};
/** /**
* Binary header prefix length * Binary header prefix length
*/ */

View File

@ -0,0 +1,134 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.NetUtil;
import java.util.List;
import static io.netty.handler.codec.haproxy.HAProxyConstants.*;
/**
* Encodes an HAProxy proxy protocol message
*
* @see <a href="http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
*/
@Sharable
public final class HAProxyMessageEncoder extends MessageToByteEncoder<HAProxyMessage> {
private static final int V2_VERSION_BITMASK = 0x02 << 4;
// Length for source/destination addresses for the UNIX family must be 108 bytes each.
static final int UNIX_ADDRESS_BYTES_LENGTH = 108;
static final int TOTAL_UNIX_ADDRESS_BYTES_LENGTH = UNIX_ADDRESS_BYTES_LENGTH * 2;
public static final HAProxyMessageEncoder INSTANCE = new HAProxyMessageEncoder();
private HAProxyMessageEncoder() {
}
@Override
protected void encode(ChannelHandlerContext ctx, HAProxyMessage msg, ByteBuf out) throws Exception {
switch (msg.protocolVersion()) {
case V1:
encodeV1(msg, out);
break;
case V2:
encodeV2(msg, out);
break;
default:
throw new HAProxyProtocolException("Unsupported version: " + msg.protocolVersion());
}
}
private static void encodeV1(HAProxyMessage msg, ByteBuf out) {
out.writeBytes(TEXT_PREFIX);
out.writeByte((byte) ' ');
out.writeCharSequence(msg.proxiedProtocol().name(), CharsetUtil.US_ASCII);
out.writeByte((byte) ' ');
out.writeCharSequence(msg.sourceAddress(), CharsetUtil.US_ASCII);
out.writeByte((byte) ' ');
out.writeCharSequence(msg.destinationAddress(), CharsetUtil.US_ASCII);
out.writeByte((byte) ' ');
out.writeCharSequence(String.valueOf(msg.sourcePort()), CharsetUtil.US_ASCII);
out.writeByte((byte) ' ');
out.writeCharSequence(String.valueOf(msg.destinationPort()), CharsetUtil.US_ASCII);
out.writeByte((byte) '\r');
out.writeByte((byte) '\n');
}
private static void encodeV2(HAProxyMessage msg, ByteBuf out) {
out.writeBytes(BINARY_PREFIX);
out.writeByte(V2_VERSION_BITMASK | msg.command().byteValue());
out.writeByte(msg.proxiedProtocol().byteValue());
switch (msg.proxiedProtocol().addressFamily()) {
case AF_IPv4:
case AF_IPv6:
byte[] srcAddrBytes = NetUtil.createByteArrayFromIpAddressString(msg.sourceAddress());
byte[] dstAddrBytes = NetUtil.createByteArrayFromIpAddressString(msg.destinationAddress());
// srcAddrLen + dstAddrLen + 4 (srcPort + dstPort) + numTlvBytes
out.writeShort(srcAddrBytes.length + dstAddrBytes.length + 4 + msg.tlvNumBytes());
out.writeBytes(srcAddrBytes);
out.writeBytes(dstAddrBytes);
out.writeShort(msg.sourcePort());
out.writeShort(msg.destinationPort());
encodeTlvs(msg.tlvs(), out);
break;
case AF_UNIX:
out.writeShort(TOTAL_UNIX_ADDRESS_BYTES_LENGTH + msg.tlvNumBytes());
int srcAddrBytesWritten = out.writeCharSequence(msg.sourceAddress(), CharsetUtil.US_ASCII);
out.writeZero(UNIX_ADDRESS_BYTES_LENGTH - srcAddrBytesWritten);
int dstAddrBytesWritten = out.writeCharSequence(msg.destinationAddress(), CharsetUtil.US_ASCII);
out.writeZero(UNIX_ADDRESS_BYTES_LENGTH - dstAddrBytesWritten);
encodeTlvs(msg.tlvs(), out);
break;
case AF_UNSPEC:
out.writeShort(0);
break;
default:
throw new HAProxyProtocolException("unexpected addrFamily");
}
}
private static void encodeTlv(HAProxyTLV haProxyTLV, ByteBuf out) {
if (haProxyTLV instanceof HAProxySSLTLV) {
HAProxySSLTLV ssltlv = (HAProxySSLTLV) haProxyTLV;
out.writeByte(haProxyTLV.typeByteValue());
out.writeShort(ssltlv.contentNumBytes());
out.writeByte(ssltlv.client());
out.writeInt(ssltlv.verify());
encodeTlvs(ssltlv.encapsulatedTLVs(), out);
} else {
out.writeByte(haProxyTLV.typeByteValue());
ByteBuf value = haProxyTLV.content();
int readableBytes = value.readableBytes();
out.writeShort(readableBytes);
out.writeBytes(value.readSlice(readableBytes));
}
}
private static void encodeTlvs(List<HAProxyTLV> haProxyTLVs, ByteBuf out) {
for (int i = 0; i < haProxyTLVs.size(); i++) {
encodeTlv(haProxyTLVs.get(i), out);
}
}
}

View File

@ -17,6 +17,8 @@
package io.netty.handler.codec.haproxy; package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.internal.StringUtil;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -35,7 +37,19 @@ public final class HAProxySSLTLV extends HAProxyTLV {
* Creates a new HAProxySSLTLV * Creates a new HAProxySSLTLV
* *
* @param verify the verification result as defined in the specification for the pp2_tlv_ssl struct (see * @param verify the verification result as defined in the specification for the pp2_tlv_ssl struct (see
* http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt) * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
* @param clientBitField the bitfield with client information
* @param tlvs the encapsulated {@link HAProxyTLV}s
*/
public HAProxySSLTLV(final int verify, final byte clientBitField, final List<HAProxyTLV> tlvs) {
this(verify, clientBitField, tlvs, Unpooled.EMPTY_BUFFER);
}
/**
* Creates a new HAProxySSLTLV
*
* @param verify the verification result as defined in the specification for the pp2_tlv_ssl struct (see
* http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
* @param clientBitField the bitfield with client information * @param clientBitField the bitfield with client information
* @param tlvs the encapsulated {@link HAProxyTLV}s * @param tlvs the encapsulated {@link HAProxyTLV}s
* @param rawContent the raw TLV content * @param rawContent the raw TLV content
@ -69,6 +83,13 @@ public final class HAProxySSLTLV extends HAProxyTLV {
return (clientBitField & 0x4) != 0; return (clientBitField & 0x4) != 0;
} }
/**
* Returns the client bit field
*/
public byte client() {
return clientBitField;
}
/** /**
* Returns the verification result * Returns the verification result
*/ */
@ -83,4 +104,22 @@ public final class HAProxySSLTLV extends HAProxyTLV {
return tlvs; return tlvs;
} }
@Override
int contentNumBytes() {
int tlvNumBytes = 0;
for (int i = 0; i < tlvs.size(); i++) {
tlvNumBytes += tlvs.get(i).totalNumBytes();
}
return 5 + tlvNumBytes; // clientBit(1) + verify(4) + tlvs
}
@Override
public String toString() {
return StringUtil.simpleClassName(this) +
"(type: " + type() +
", typeByteValue: " + typeByteValue() +
", client: " + client() +
", verify: " + verify() +
", numEncapsulatedTlvs: " + tlvs.size() + ')';
}
} }

View File

@ -18,6 +18,7 @@ package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.DefaultByteBufHolder; import io.netty.buffer.DefaultByteBufHolder;
import io.netty.util.internal.StringUtil;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
@ -32,6 +33,18 @@ public class HAProxyTLV extends DefaultByteBufHolder {
private final Type type; private final Type type;
private final byte typeByteValue; private final byte typeByteValue;
/**
* The size of this tlv in bytes.
* @return the number of bytes.
*/
int totalNumBytes() {
return 3 + contentNumBytes(); // type(1) + length(2) + content
}
int contentNumBytes() {
return content().readableBytes();
}
/** /**
* The registered types a TLV can have regarding the PROXY protocol 1.5 spec * The registered types a TLV can have regarding the PROXY protocol 1.5 spec
*/ */
@ -56,7 +69,7 @@ public class HAProxyTLV extends DefaultByteBufHolder {
* *
* @return the {@link Type} of a TLV * @return the {@link Type} of a TLV
*/ */
public static Type typeForByteValue(final byte byteValue) { public static Type typeForByteValue(byte byteValue) {
switch (byteValue) { switch (byteValue) {
case 0x01: case 0x01:
return PP2_TYPE_ALPN; return PP2_TYPE_ALPN;
@ -74,6 +87,52 @@ public class HAProxyTLV extends DefaultByteBufHolder {
return OTHER; return OTHER;
} }
} }
/**
* Returns the byte value for the {@link Type} as defined in the PROXY protocol 1.5 spec.
*
* @param type the {@link Type}
*
* @return the byte value of the {@link Type}.
*/
public static byte byteValueForType(Type type) {
switch (type) {
case PP2_TYPE_ALPN:
return 0x01;
case PP2_TYPE_AUTHORITY:
return 0x02;
case PP2_TYPE_SSL:
return 0x20;
case PP2_TYPE_SSL_VERSION:
return 0x21;
case PP2_TYPE_SSL_CN:
return 0x22;
case PP2_TYPE_NETNS:
return 0x30;
default:
throw new IllegalArgumentException("unknown type: " + type);
}
}
}
/**
* Creates a new HAProxyTLV
*
* @param typeByteValue the byteValue of the TLV. This is especially important if non-standard TLVs are used
* @param content the raw content of the TLV
*/
public HAProxyTLV(byte typeByteValue, ByteBuf content) {
this(Type.typeForByteValue(typeByteValue), typeByteValue, content);
}
/**
* Creates a new HAProxyTLV
*
* @param type the {@link Type} of the TLV
* @param content the raw content of the TLV
*/
public HAProxyTLV(Type type, ByteBuf content) {
this(type, Type.byteValueForType(type), content);
} }
/** /**
@ -148,4 +207,12 @@ public class HAProxyTLV extends DefaultByteBufHolder {
super.touch(hint); super.touch(hint);
return this; return this;
} }
@Override
public String toString() {
return StringUtil.simpleClassName(this) +
"(type: " + type() +
", typeByteValue: " + typeByteValue() +
", content: " + contentToString() + ')';
}
} }

View File

@ -0,0 +1,96 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.handler.codec.haproxy;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.local.LocalAddress;
import io.netty.channel.local.LocalChannel;
import io.netty.channel.local.LocalHandler;
import io.netty.channel.local.LocalServerChannel;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
public class HAProxyIntegrationTest {
@Test
public void testBasicCase() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<HAProxyMessage> msgHolder = new AtomicReference<>();
LocalAddress localAddress = new LocalAddress("HAProxyIntegrationTest");
EventLoopGroup group = new MultithreadEventLoopGroup(LocalHandler.newFactory());
ServerBootstrap sb = new ServerBootstrap();
sb.channel(LocalServerChannel.class)
.group(group)
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) {
ch.pipeline().addLast(new HAProxyMessageDecoder());
ch.pipeline().addLast(new SimpleChannelInboundHandler<HAProxyMessage>() {
@Override
protected void messageReceived(ChannelHandlerContext ctx, HAProxyMessage msg) {
msgHolder.set(msg.retain());
latch.countDown();
}
});
}
});
Channel serverChannel = sb.bind(localAddress).sync().channel();
Bootstrap b = new Bootstrap();
Channel clientChannel = b.channel(LocalChannel.class)
.handler(HAProxyMessageEncoder.INSTANCE)
.group(group)
.connect(localAddress).sync().channel();
try {
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"192.168.0.1", "192.168.0.11", 56324, 443);
clientChannel.writeAndFlush(message).sync();
assertTrue(latch.await(5, TimeUnit.SECONDS));
HAProxyMessage readMessage = msgHolder.get();
assertEquals(message.protocolVersion(), readMessage.protocolVersion());
assertEquals(message.command(), readMessage.command());
assertEquals(message.proxiedProtocol(), readMessage.proxiedProtocol());
assertEquals(message.sourceAddress(), readMessage.sourceAddress());
assertEquals(message.destinationAddress(), readMessage.destinationAddress());
assertEquals(message.sourcePort(), readMessage.sourcePort());
assertEquals(message.destinationPort(), readMessage.destinationPort());
readMessage.release();
} finally {
clientChannel.close().sync();
serverChannel.close().sync();
group.shutdownGracefully().sync();
}
}
}

View File

@ -0,0 +1,404 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.haproxy.HAProxyTLV.Type;
import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static io.netty.handler.codec.haproxy.HAProxyConstants.*;
import static io.netty.handler.codec.haproxy.HAProxyMessageEncoder.*;
import static org.junit.Assert.*;
public class HaProxyMessageEncoderTest {
private static final int V2_HEADER_BYTES_LENGTH = 16;
private static final int IPv4_ADDRESS_BYTES_LENGTH = 12;
private static final int IPv6_ADDRESS_BYTES_LENGTH = 36;
@Test
public void testIPV4EncodeProxyV1() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"192.168.0.1", "192.168.0.11", 56324, 443);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
assertEquals("PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n",
byteBuf.toString(CharsetUtil.US_ASCII));
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testIPV6EncodeProxyV1() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP6,
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "1050:0:0:0:5:600:300c:326b", 56324, 443);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
assertEquals("PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 1050:0:0:0:5:600:300c:326b 56324 443\r\n",
byteBuf.toString(CharsetUtil.US_ASCII));
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testIPv4EncodeProxyV2() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"192.168.0.1", "192.168.0.11", 56324, 443);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
// header
byte[] headerBytes = ByteBufUtil.getBytes(byteBuf, 0, 12);
assertArrayEquals(BINARY_PREFIX, headerBytes);
// command
byte commandByte = byteBuf.getByte(12);
assertEquals(0x02, (commandByte & 0xf0) >> 4);
assertEquals(0x01, commandByte & 0x0f);
// transport protocol, address family
byte transportByte = byteBuf.getByte(13);
assertEquals(0x01, (transportByte & 0xf0) >> 4);
assertEquals(0x01, transportByte & 0x0f);
// source address length
int sourceAddrLength = byteBuf.getUnsignedShort(14);
assertEquals(12, sourceAddrLength);
// source address
byte[] sourceAddr = ByteBufUtil.getBytes(byteBuf, 16, 4);
assertArrayEquals(new byte[] { (byte) 0xc0, (byte) 0xa8, 0x00, 0x01 }, sourceAddr);
// destination address
byte[] destAddr = ByteBufUtil.getBytes(byteBuf, 20, 4);
assertArrayEquals(new byte[] { (byte) 0xc0, (byte) 0xa8, 0x00, 0x0b }, destAddr);
// source port
int sourcePort = byteBuf.getUnsignedShort(24);
assertEquals(56324, sourcePort);
// destination port
int destPort = byteBuf.getUnsignedShort(26);
assertEquals(443, destPort);
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testIPv6EncodeProxyV2() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP6,
"2001:0db8:85a3:0000:0000:8a2e:0370:7334", "1050:0:0:0:5:600:300c:326b", 56324, 443);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
// header
byte[] headerBytes = ByteBufUtil.getBytes(byteBuf, 0, 12);
assertArrayEquals(BINARY_PREFIX, headerBytes);
// command
byte commandByte = byteBuf.getByte(12);
assertEquals(0x02, (commandByte & 0xf0) >> 4);
assertEquals(0x01, commandByte & 0x0f);
// transport protocol, address family
byte transportByte = byteBuf.getByte(13);
assertEquals(0x02, (transportByte & 0xf0) >> 4);
assertEquals(0x01, transportByte & 0x0f);
// source address length
int sourceAddrLength = byteBuf.getUnsignedShort(14);
assertEquals(IPv6_ADDRESS_BYTES_LENGTH, sourceAddrLength);
// source address
byte[] sourceAddr = ByteBufUtil.getBytes(byteBuf, 16, 16);
assertArrayEquals(new byte[] {
(byte) 0x20, (byte) 0x01, 0x0d, (byte) 0xb8,
(byte) 0x85, (byte) 0xa3, 0x00, 0x00, 0x00, 0x00, (byte) 0x8a, 0x2e,
0x03, 0x70, 0x73, 0x34
}, sourceAddr);
// destination address
byte[] destAddr = ByteBufUtil.getBytes(byteBuf, 32, 16);
assertArrayEquals(new byte[] {
(byte) 0x10, (byte) 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x06, 0x00, 0x30, 0x0c, 0x32, 0x6b
}, destAddr);
// source port
int sourcePort = byteBuf.getUnsignedShort(48);
assertEquals(56324, sourcePort);
// destination port
int destPort = byteBuf.getUnsignedShort(50);
assertEquals(443, destPort);
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testUnixEncodeProxyV2() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNIX_STREAM,
"/var/run/src.sock", "/var/run/dst.sock", 0, 0);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
// header
byte[] headerBytes = ByteBufUtil.getBytes(byteBuf, 0, 12);
assertArrayEquals(BINARY_PREFIX, headerBytes);
// command
byte commandByte = byteBuf.getByte(12);
assertEquals(0x02, (commandByte & 0xf0) >> 4);
assertEquals(0x01, commandByte & 0x0f);
// transport protocol, address family
byte transportByte = byteBuf.getByte(13);
assertEquals(0x03, (transportByte & 0xf0) >> 4);
assertEquals(0x01, transportByte & 0x0f);
// address length
int addrLength = byteBuf.getUnsignedShort(14);
assertEquals(TOTAL_UNIX_ADDRESS_BYTES_LENGTH, addrLength);
// source address
int srcAddrEnd = byteBuf.forEachByte(16, 108, ByteProcessor.FIND_NUL);
assertEquals("/var/run/src.sock",
byteBuf.slice(16, srcAddrEnd - 16).toString(CharsetUtil.US_ASCII));
// destination address
int dstAddrEnd = byteBuf.forEachByte(124, 108, ByteProcessor.FIND_NUL);
assertEquals("/var/run/dst.sock",
byteBuf.slice(124, dstAddrEnd - 124).toString(CharsetUtil.US_ASCII));
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testTLVEncodeProxy() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
List<HAProxyTLV> tlvs = new ArrayList<HAProxyTLV>();
ByteBuf helloWorld = Unpooled.copiedBuffer("hello world", CharsetUtil.US_ASCII);
HAProxyTLV alpnTlv = new HAProxyTLV(Type.PP2_TYPE_ALPN, (byte) 0x01, helloWorld.copy());
tlvs.add(alpnTlv);
ByteBuf arbitrary = Unpooled.copiedBuffer("an arbitrary string", CharsetUtil.US_ASCII);
HAProxyTLV authorityTlv = new HAProxyTLV(Type.PP2_TYPE_AUTHORITY, (byte) 0x01, arbitrary.copy());
tlvs.add(authorityTlv);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"192.168.0.1", "192.168.0.11", 56324, 443, tlvs);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
// length
assertEquals(byteBuf.getUnsignedShort(14), byteBuf.readableBytes() - V2_HEADER_BYTES_LENGTH);
// skip to tlv section
ByteBuf tlv = byteBuf.skipBytes(V2_HEADER_BYTES_LENGTH + IPv4_ADDRESS_BYTES_LENGTH);
// alpn tlv
assertEquals(alpnTlv.typeByteValue(), tlv.readByte());
short bufLength = tlv.readShort();
assertEquals(helloWorld.array().length, bufLength);
assertEquals(helloWorld, tlv.readBytes(bufLength));
// authority tlv
assertEquals(authorityTlv.typeByteValue(), tlv.readByte());
bufLength = tlv.readShort();
assertEquals(arbitrary.array().length, bufLength);
assertEquals(arbitrary, tlv.readBytes(bufLength));
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testSslTLVEncodeProxy() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
List<HAProxyTLV> tlvs = new ArrayList<HAProxyTLV>();
ByteBuf helloWorld = Unpooled.copiedBuffer("hello world", CharsetUtil.US_ASCII);
HAProxyTLV alpnTlv = new HAProxyTLV(Type.PP2_TYPE_ALPN, (byte) 0x01, helloWorld.copy());
tlvs.add(alpnTlv);
ByteBuf arbitrary = Unpooled.copiedBuffer("an arbitrary string", CharsetUtil.US_ASCII);
HAProxyTLV authorityTlv = new HAProxyTLV(Type.PP2_TYPE_AUTHORITY, (byte) 0x01, arbitrary.copy());
tlvs.add(authorityTlv);
ByteBuf sslContent = Unpooled.copiedBuffer("some ssl content", CharsetUtil.US_ASCII);
HAProxySSLTLV haProxySSLTLV = new HAProxySSLTLV(1, (byte) 0x01, tlvs, sslContent.copy());
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"192.168.0.1", "192.168.0.11", 56324, 443,
Collections.<HAProxyTLV>singletonList(haProxySSLTLV));
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
assertEquals(byteBuf.getUnsignedShort(14), byteBuf.readableBytes() - V2_HEADER_BYTES_LENGTH);
ByteBuf tlv = byteBuf.skipBytes(V2_HEADER_BYTES_LENGTH + IPv4_ADDRESS_BYTES_LENGTH);
// ssl tlv type
assertEquals(haProxySSLTLV.typeByteValue(), tlv.readByte());
// length
int bufLength = tlv.readUnsignedShort();
assertEquals(bufLength, tlv.readableBytes());
// client, verify
assertEquals(0x01, byteBuf.readByte());
assertEquals(1, byteBuf.readInt());
// alpn tlv
assertEquals(alpnTlv.typeByteValue(), tlv.readByte());
bufLength = tlv.readShort();
assertEquals(helloWorld.array().length, bufLength);
assertEquals(helloWorld, tlv.readBytes(bufLength));
// authority tlv
assertEquals(authorityTlv.typeByteValue(), tlv.readByte());
bufLength = tlv.readShort();
assertEquals(arbitrary.array().length, bufLength);
assertEquals(arbitrary, tlv.readBytes(bufLength));
byteBuf.release();
assertFalse(ch.finish());
}
@Test
public void testEncodeLocalProxyV2() {
EmbeddedChannel ch = new EmbeddedChannel(INSTANCE);
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL, HAProxyProxiedProtocol.UNKNOWN,
null, null, 0, 0);
assertTrue(ch.writeOutbound(message));
ByteBuf byteBuf = ch.readOutbound();
// header
byte[] headerBytes = new byte[12];
byteBuf.readBytes(headerBytes);
assertArrayEquals(BINARY_PREFIX, headerBytes);
// command
byte commandByte = byteBuf.readByte();
assertEquals(0x02, (commandByte & 0xf0) >> 4);
assertEquals(0x00, commandByte & 0x0f);
// transport protocol, address family
byte transportByte = byteBuf.readByte();
assertEquals(0x00, transportByte);
// source address length
int sourceAddrLength = byteBuf.readUnsignedShort();
assertEquals(0, sourceAddrLength);
assertFalse(byteBuf.isReadable());
byteBuf.release();
assertFalse(ch.finish());
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidIpV4Address() {
String invalidIpv4Address = "192.168.0.1234";
new HAProxyMessage(
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
invalidIpv4Address, "192.168.0.11", 56324, 443);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidIpV6Address() {
String invalidIpv6Address = "2001:0db8:85a3:0000:0000:8a2e:0370:73345";
new HAProxyMessage(
HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP6,
invalidIpv6Address, "1050:0:0:0:5:600:300c:326b", 56324, 443);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidUnixAddress() {
String invalidUnixAddress = new String(new byte[UNIX_ADDRESS_BYTES_LENGTH + 1]);
new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNIX_STREAM,
invalidUnixAddress, "/var/run/dst.sock", 0, 0);
}
@Test(expected = NullPointerException.class)
public void testNullUnixAddress() {
new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNIX_STREAM,
null, null, 0, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testLongUnixAddress() {
String longUnixAddress = new String(new char[109]).replace("\0", "a");
new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNIX_STREAM,
"source", longUnixAddress, 0, 0);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidUnixPort() {
new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNIX_STREAM,
"/var/run/src.sock", "/var/run/dst.sock", 80, 443);
}
}

View File

@ -103,6 +103,11 @@
<artifactId>netty-codec-mqtt</artifactId> <artifactId>netty-codec-mqtt</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netty-codec-haproxy</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.google.protobuf</groupId> <groupId>com.google.protobuf</groupId>

View File

@ -0,0 +1,60 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.example.haproxy;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.haproxy.HAProxyCommand;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.haproxy.HAProxyProtocolVersion;
import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
import io.netty.util.CharsetUtil;
import static io.netty.example.haproxy.HAProxyServer.*;
public final class HAProxyClient {
private static final String HOST = System.getProperty("host", "127.0.0.1");
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new HAProxyHandler());
// Start the connection attempt.
Channel ch = b.connect(HOST, PORT).sync().channel();
HAProxyMessage message = new HAProxyMessage(
HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.TCP4,
"127.0.0.1", "127.0.0.2", 8000, 9000);
ch.writeAndFlush(message).sync();
ch.writeAndFlush(Unpooled.copiedBuffer("Hello World!", CharsetUtil.US_ASCII)).sync();
ch.writeAndFlush(Unpooled.copiedBuffer("Bye now!", CharsetUtil.US_ASCII)).sync();
ch.close().sync();
} finally {
group.shutdownGracefully();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.example.haproxy;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.haproxy.HAProxyMessageEncoder;
public class HAProxyHandler extends ChannelOutboundHandlerAdapter {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
ctx.pipeline().addBefore(ctx.name(), null, HAProxyMessageEncoder.INSTANCE);
super.handlerAdded(ctx);
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ChannelFuture future = ctx.write(msg, promise);
if (msg instanceof HAProxyMessage) {
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
ctx.pipeline().remove(HAProxyMessageEncoder.INSTANCE);
ctx.pipeline().remove(HAProxyHandler.this);
} else {
ctx.close();
}
}
});
}
}
}

View File

@ -0,0 +1,73 @@
/*
* Copyright 2020 The Netty Project
*
* The Netty Project licenses this file to you 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 io.netty.example.haproxy;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.MultithreadEventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.haproxy.HAProxyMessage;
import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
public final class HAProxyServer {
static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new MultithreadEventLoopGroup(1, NioHandler.newFactory());
EventLoopGroup workerGroup = new MultithreadEventLoopGroup(NioHandler.newFactory());
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HAProxyServerInitializer());
b.bind(PORT).sync().channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
static class HAProxyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new LoggingHandler(LogLevel.DEBUG),
new HAProxyMessageDecoder(),
new SimpleChannelInboundHandler<Object>() {
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HAProxyMessage) {
System.out.println("proxy message: " + msg);
} else if (msg instanceof ByteBuf) {
System.out.println("bytebuf message: " + ByteBufUtil.prettyHexDump((ByteBuf) msg));
}
}
});
}
}
}