Is there a way (and how) to know the status of a connection pool? Like, how many connections are being used, how many are available, ...
We are currently facing issues where the application cannot get a connection from the pool (ConnectionPoolTimeoutException: Timeout waiting for connection from pool) so to track down the cause we would like to log some pool stats each time a new connection is requested.
I have been browsing the Apache HTTPClient API but did not find a way to get this information.
We use PoolingClientConnectionManager.
You can use methods of the ConnPoolControl interface to control parameters of the internal pool
You can have a detailed information total and per route with the code below:
public static void main(String[] args) {
PoolingHttpClientConnectionManager connectionManager = HttpClientUtils.getConnectionManager();
System.out.println(createHttpInfo(connectionManager));
}
private static String createHttpInfo(PoolingHttpClientConnectionManager connectionManager) {
StringBuilder sb = new StringBuilder();
sb.append("=========================").append("\n");
sb.append("General Info:").append("\n");
sb.append("-------------------------").append("\n");
sb.append("MaxTotal: ").append(connectionManager.getMaxTotal()).append("\n");
sb.append("DefaultMaxPerRoute: ").append(connectionManager.getDefaultMaxPerRoute()).append("\n");
sb.append("ValidateAfterInactivity: ").append(connectionManager.getValidateAfterInactivity()).append("\n");
sb.append("=========================").append("\n");
PoolStats totalStats = connectionManager.getTotalStats();
sb.append(createPoolStatsInfo("Total Stats", totalStats));
Set<HttpRoute> routes = connectionManager.getRoutes();
if (routes != null) {
for (HttpRoute route : routes) {
sb.append(createRouteInfo(connectionManager, route));
}
}
return sb.toString();
}
private static String createRouteInfo(PoolingHttpClientConnectionManager connectionManager, HttpRoute route) {
PoolStats routeStats = connectionManager.getStats(route);
String info = createPoolStatsInfo(route.getTargetHost().toURI(), routeStats);
return info;
}
private static String createPoolStatsInfo(String title, PoolStats poolStats) {
StringBuilder sb = new StringBuilder();
sb.append(title + ":").append("\n");
sb.append("-------------------------").append("\n");
if (poolStats != null) {
sb.append("Available: ").append(poolStats.getAvailable()).append("\n");
sb.append("Leased: ").append(poolStats.getLeased()).append("\n");
sb.append("Max: ").append(poolStats.getMax()).append("\n");
sb.append("Pending: ").append(poolStats.getPending()).append("\n");
}
sb.append("=========================").append("\n");
return sb.toString();
}
Update (2019-01-07)
The connection manager is retrieved from an utilitarian class I've created (you can create it differently):
public class HttpClientUtils {
private static final PoolingHttpClientConnectionManager connectionManager = createConnectionManager();
private static PoolingHttpClientConnectionManager createConnectionManager() {
try {
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
SSLContext.getDefault(),
new String[] {"TLSv1", "TLSv1.1", "TLSv1.2"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.INSTANCE)
.register("https", socketFactory)
.build();
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(200);
cm.setDefaultMaxPerRoute(20);
return cm;
} catch (NoSuchAlgorithmException | RuntimeException ex) {
Logger.getLogger(HttpClientUtils.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
public static PoolingHttpClientConnectionManager getConnectionManager() {
return connectionManager;
}
}
Related
I am adding PoolingHttpClientConnectionManager to our Spring Boot service, which is currently using RestTemplate and CloseableHttpClient to make requests. The service is using Apache httpcomponents 4.5.
What happens if there is a temporary network outage? Do the bad connections get discarded from the pool? Or does the pool fill up with bad connections during the outage? Once the outage is over, does the pool recover on its own? Or do I need to write code to detect them and remove them from the pool?
Here is the code I have so far:
#Bean
public CloseableHttpClient closeableHttpClient() {
CloseableHttpClient client = HttpClients.custom()
.setDefaultRequestConfig(getDefaultRequestConfig())
.setConnectionManager(poolingHttpClientConnectionManager())
.setKeepAliveStrategy(getConnectionKeepAliveStrategy())
.setRetryHandler(getHttpRequestRetryHandler())
.build();
return client;
}
#Bean
public PoolingHttpClientConnectionManager poolingHttpClientConnectionManager() {
Registry<ConnectionSocketFactory> socketFactoryRegistry = getSocketFactoryRegistry();
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
poolingConnectionManager.setMaxTotal(maxTotalConnections);
poolingConnectionManager.setDefaultMaxPerRoute(maxRouteConnections);
return poolingConnectionManager;
}
#Bean
public Runnable idleConnectionMonitor(PoolingHttpClientConnectionManager pool) {
return new Runnable() {
#Override
#Scheduled(fixedDelay = 20000)
public void run() {
if (pool != null) {
pool.closeExpiredConnections();
pool.closeIdleConnections(idleConnectionCloseTime, TimeUnit.MILLISECONDS);
}
}
};
}
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setThreadNamePrefix("idleMonitor");
scheduler.setPoolSize(idleMonitorPoolSize);
return scheduler;
}
private RequestConfig getDefaultRequestConfig() {
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(establishConnectionTimeout)
.setConnectionRequestTimeout(fetchConnectionTimeout)
.setSocketTimeout(socketInactivityTimeout)
.build();
return requestConfig;
}
private Registry<ConnectionSocketFactory> getSocketFactoryRegistry() {
SSLConnectionSocketFactory socketFactory;
socketFactory = new SSLConnectionSocketFactory(getSSLContext(), new String[] { sslProtocol }, null, new DefaultHostnameVerifier());
Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
.<ConnectionSocketFactory> create().register("https", socketFactory)
.build();
return socketFactoryRegistry;
}
private SSLContext getSSLContext() {
TrustStrategy acceptingTrustStrategy = null;
try {
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(new FileInputStream(new File(keyStoreFile)), keyStorePwd.toCharArray());
return new SSLContextBuilder()
.loadTrustMaterial(keyStore, acceptingTrustStrategy)
.loadKeyMaterial(keyStore, keyStorePwd.toCharArray())
.build();
} catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | KeyManagementException | UnrecoverableKeyException e) {
throw new RuntimeException(e);
}
}
private ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
return (httpResponse, httpContext) -> {
HeaderIterator headerIterator = httpResponse.headerIterator(HTTP.CONN_KEEP_ALIVE);
HeaderElementIterator elementIterator = new BasicHeaderElementIterator(headerIterator);
while (elementIterator.hasNext()) {
HeaderElement element = elementIterator.nextElement();
String param = element.getName();
String value = element.getValue();
if (value != null && param.equalsIgnoreCase("timeout")) {
return Long.parseLong(value) * 1000; // convert to ms
}
}
return defaultKeepAliveTime;
};
}
private DefaultHttpRequestRetryHandler getHttpRequestRetryHandler() {
return new DefaultHttpRequestRetryHandler(httprequestRetryCount, false);
}
Those connections become invalid or "stale". If HttpClient has been configured to validate connections prior to using them to execute requests, stale connections will be detected and discarded.
I have written a netty client code to send some processed data to multiple clients. After running for 3-4 hours I exhaust all sockets and no more connections possible. Also when I check the socket states in the OS a large number of sockets are in TIME_WAIT state.
public class NettyClient {
private static LogHelper logger = new LogHelper(NettyClient.class);
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static Bootstrap nettyClient = new Bootstrap()
.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
private URL url;
private RequestVo Req;
private ChannelFuture chFuture;
private Object ReportData;
private JAXBContext jbContext;
private static final int CHANNEL_READ_TIMEOUT = 5;
public NettyClient() {
// TODO Auto-generated constructor stub
}
public NettyClient(RequestVo Req, JAXBContext jbCtx,Object data) {
this.Req = Req;
this.ReportData = data;
this.jbContext = jbCtx;
}
public void sendRequest() {
logger.debug("In sendRequest()");
//ChannelFuture chFuture = null;
try {
this.url = new URL(Req.getPushAddress());
//add handlers
nettyClient.handler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast("timeout",
new ReadTimeoutHandler(CHANNEL_READ_TIMEOUT, TimeUnit.SECONDS));
ch.pipeline()
.addLast("codec", new HttpClientCodec());
ch.pipeline()
.addLast("inbound",
new NettyClientInBoundHandler(Req, jbContext, ReportData));
}
});
//make a connection to the Client
int port = url.getPort() == -1? url.getDefaultPort():url.getPort();
chFuture = nettyClient.connect(url.getHost(), port);
chFuture.addListener(new NettyClientConnectionListener(this.Req.getRequestId()));
} catch (Exception e) {
logger.error("Exception: Failed to connect to Client ", e);
} finally {
}
}
}
Here are the methods from ChannelInBoundHandler Class
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
{
Map<String, String> props = new HashMap<String, String>();
if(msg instanceof HttpResponse) {
logger.debug("channelRead()");
HttpResponse httpRes = (HttpResponse) msg;
HttpResponseStatus httpStatus = httpRes.status();
props.put(REQUEST_ID, this.Request.getRequestId());
props.put(CLIENT_RESPONSE_CODE, String.valueOf(httpStatus.code()));
JmsService.getInstance(DESTINATION).sendTextMessage(props, "");
logger.debug("channelRead() HttpResponse Code: " + httpStatus.code());
ctx.close();
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
{
Map<String, String> props = new HashMap<String, String>();
logger.error("exceptionCaught()", cause);
if(cause instanceof ReadTimeoutException) {
//If read-timeout, send back the response
props.put(REQUEST_ID, this.Request.getRequestId());
props.put(CLIENT_RESPONSE_CODE,
String.valueOf(HttpResponseStatus.REQUEST_TIMEOUT.code()));
JmsService.getInstance(DESTINATION).sendTextMessage(props, "");
ctx.close();
}
else {
logger.error("Exception: ", cause);
}
}
Any idea what is wrong in the code would greatly help me.
Thanks
I'm not familiar with netty, but I think I can explain part of your problem, and hopefully help you along the way:
When you make use of a port and then close it, the port will not automatically be available for use by other processes at once. Instead, it will go into the TIME_WAIT state for a certain period of time. For Windows, I believe this will be 240 seconds (four minutes).
I'd guess that your code is slowly using up all the available ports on your system, due to the release of ports from the TIME_WAIT state is happening too slowly.
It's not entirely clear to me where the actual port numbers are coming from (are they auto-generated by url.getDefaultPort() perhaps?), but perhaps you can find some way to reuse them? If you can keep one or more open connections and somehow reuse these, then you might be able to decrease the frequency of requests for new ports enough for the closed ports to go out of their TIME_WAIT state.
I am learning zookeeper and trying out the Curator framework for service discoveries. However, I am facing a weird issue that I have difficulties to figure out. The problem is when I tried to register an instance via serviceDiscovery, the cacheChanged event of the serviceCache gets triggered three times. When I removed an instance, it is only triggered once, which is the expected behavior. Please see the code below:
public class DiscoveryExample {
private static String PATH = "/base";
static ServiceDiscovery<InstanceDetails> serviceDiscovery = null;
public static void main(String[] args) throws Exception {
CuratorFramework client = null;
try {
// this is the ip address of my VM
client = CuratorFrameworkFactory.newClient("192.168.149.129:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
JsonInstanceSerializer<InstanceDetails> serializer = new JsonInstanceSerializer<InstanceDetails>(
InstanceDetails.class);
serviceDiscovery = ServiceDiscoveryBuilder.builder(InstanceDetails.class)
.client(client)
.basePath(PATH)
.serializer(serializer)
.build();
serviceDiscovery.start();
ServiceCache<InstanceDetails> serviceCache = serviceDiscovery.serviceCacheBuilder()
.name("product")
.build();
serviceCache.addListener(new ServiceCacheListener() {
#Override
public void stateChanged(CuratorFramework curator, ConnectionState state) {
// TODO Auto-generated method stub
System.out.println("State Changed to " + state.name());
}
// THIS IS THE PART GETS TRIGGERED MULTIPLE TIMES
#Override
public void cacheChanged() {
System.out.println("Cached Changed ");
List<ServiceInstance<InstanceDetails>> list = serviceCache.getInstances();
Iterator<ServiceInstance<InstanceDetails>> it = list.iterator();
while(it.hasNext()) {
System.out.println(it.next().getAddress());
}
}
});
serviceCache.start();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
System.out.print("> ");
String line = in.readLine();
} finally {
CloseableUtils.closeQuietly(serviceDiscovery);
CloseableUtils.closeQuietly(client);
}
}
}
AND
public class RegisterApplicationServer {
final static String PATH = "/base";
static ServiceDiscovery<InstanceDetails> serviceDiscovery = null;
public static void main(String[] args) throws Exception {
CuratorFramework client = null;
try {
client = CuratorFrameworkFactory.newClient("192.168.149.129:2181", new ExponentialBackoffRetry(1000, 3));
client.start();
JsonInstanceSerializer<InstanceDetails> serializer = new JsonInstanceSerializer<InstanceDetails>(
InstanceDetails.class);
serviceDiscovery = ServiceDiscoveryBuilder.builder(InstanceDetails.class).client(client).basePath(PATH)
.serializer(serializer).build();
serviceDiscovery.start();
// SOME OTHER CODE THAT TAKES CARES OF USER INPUT...
} finally {
CloseableUtils.closeQuietly(serviceDiscovery);
CloseableUtils.closeQuietly(client);
}
}
private static void addInstance(String[] args, CuratorFramework client, String command,
ServiceDiscovery<InstanceDetails> serviceDiscovery) throws Exception {
// simulate a new instance coming up
// in a real application, this would be a separate process
if (args.length < 2) {
System.err.println("syntax error (expected add <name> <description>): " + command);
return;
}
StringBuilder description = new StringBuilder();
for (int i = 1; i < args.length; ++i) {
if (i > 1) {
description.append(' ');
}
description.append(args[i]);
}
String serviceName = args[0];
ApplicationServer server = new ApplicationServer(client, PATH, serviceName, description.toString());
server.start();
serviceDiscovery.registerService(server.getThisInstance());
System.out.println(serviceName + " added");
}
private static void deleteInstance(String[] args, String command, ServiceDiscovery<InstanceDetails> serviceDiscovery) throws Exception {
// in a real application, this would occur due to normal operation, a
// crash, maintenance, etc.
if (args.length != 2) {
System.err.println("syntax error (expected delete <name>): " + command);
return;
}
final String serviceName = args[0];
Collection<ServiceInstance<InstanceDetails>> set = serviceDiscovery.queryForInstances(serviceName);
Iterator<ServiceInstance<InstanceDetails>> it = set.iterator();
while (it.hasNext()) {
ServiceInstance<InstanceDetails> si = it.next();
if (si.getPayload().getDescription().indexOf(args[1]) != -1) {
serviceDiscovery.unregisterService(si);
}
}
System.out.println("Removed an instance of: " + serviceName);
}
}
I appriciate if anyone can please point out where I am doing wrong and maybe can share some good materials/examples so I can refer to. The official website and the examples on github does not help a lot.
I know that there are several similar questions that have either been answered or still outstanding, however, for the life of me...
Later Edit 2016-08-25 10:05 CST - Actually, I asked the wrong question.
The question is the following: given that I have both a netty server (taken from DiscardServer example) and a netty client - (see above) what must I do to force the DiscardServer to immediately send the client a request?
I have added an OutboundHandler to the server and to the client.
After looking at both the DiscardServer and PingPongServer examples, there is an external event occurring to kick off all the action. In the case of Discard server, it is originally waiting for a telnet connection, then will transmit whatever was in the telnet msg to the client.
In the case of PingPongServer, the SERVER is waiting on the client to initiate action.
What I want is for the Server to immediately start transmitting after connection with the client. None of the examples from netty seem to do this.
If I have missed something, and someone can point it out, much good karma.
My client:
public final class P4Listener {
static final Logger LOG;
static final String HOST;
static final int PORT;
static final Boolean SSL = Boolean.FALSE;
public static Dto DTO;
static {
LOG = LoggerFactory.getLogger(P4Listener.class);
HOST = P4ListenerProperties.getP4ServerAddress();
PORT = Integer.valueOf(P4ListenerProperties.getListenerPort());
DTO = new Dto();
}
public static String getId() { return DTO.getId(); }
public static void main(String[] args) throws Exception {
final SslContext sslCtx;
if (SSL) {
LOG.info("{} creating SslContext", getId());
sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new P4ListenerInitializer(sslCtx));
// Start the connection attempt.
LOG.debug(" {} starting connection attempt...", getId());
Channel ch = b.connect(HOST, PORT).sync().channel();
// ChannelFuture localWriteFuture = ch.writeAndFlush("ready\n");
// localWriteFuture.sync();
} finally {
group.shutdownGracefully();
}
}
}
public class P4ListenerHandler extends SimpleChannelInboundHandler<String> {
static final Logger LOG = LoggerFactory.getLogger(P4ListenerHandler.class);
static final DateTimeFormatter DTFormatter = DateTimeFormatter.ofPattern("yyyyMMdd-HHMMss.SSS");
static final String EndSOT;
static final String StartSOT;
static final String EOL = "\n";
static final ClassPathXmlApplicationContext AppContext;
static {
EndSOT = P4ListenerProperties.getEndSOT();
StartSOT = P4ListenerProperties.getStartSOT();
AppContext = new ClassPathXmlApplicationContext(new String[] { "applicationContext.xml" });
}
private final RequestValidator rv = new RequestValidator();
private JAXBContext jaxbContext = null;
private Unmarshaller jaxbUnmarshaller = null;
private boolean initialized = false;
private Dto dto;
public P4ListenerHandler() {
dto = new Dto();
}
public Dto getDto() { return dto; }
public String getId() { return getDto().getId(); }
Message convertXmlToMessage(String xml) {
if (xml == null)
throw new IllegalArgumentException("xml message is null!");
try {
jaxbContext = JAXBContext.newInstance(p4.model.xml.request.Message.class, p4.model.xml.request.Header.class,
p4.model.xml.request.Claims.class, p4.model.xml.request.Insurance.class,
p4.model.xml.request.Body.class, p4.model.xml.request.Prescriber.class,
p4.model.xml.request.PriorAuthorization.class,
p4.model.xml.request.PriorAuthorizationSupportingDocumentation.class);
jaxbUnmarshaller = jaxbContext.createUnmarshaller();
StringReader strReader = new StringReader(xml);
Message m = (Message) jaxbUnmarshaller.unmarshal(strReader);
return m;
} catch (JAXBException jaxbe) {
String error = StacktraceUtil.getCustomStackTrace(jaxbe);
LOG.error(error);
throw new P4XMLUnmarshalException("Problems when attempting to unmarshal transmission string: \n" + xml,
jaxbe);
}
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
LOG.debug("{} let server know we are ready", getId());
ctx.writeAndFlush("Ready...\n");
}
/**
* Important - this method will be renamed to
* <code><b>messageReceived(ChannelHandlerContext, I)</b></code> in netty 5.0
*
* #param ctx
* #param msg
*/
#Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
ChannelFuture lastWriteFuture = null;
LOG.debug("{} -- received message: {}", getId(), msg);
Channel channel = ctx.channel();
Message m = null;
try {
if (msg instanceof String && msg.length() > 0) {
m = convertXmlToMessage(msg);
m.setMessageStr(msg);
dto.setRequestMsg(m);
LOG.info("{}: received TIMESTAMP: {}", dto.getId(), LocalDateTime.now().format(DTFormatter));
LOG.debug("{}: received from server: {}", dto.getId(), msg);
/*
* theoretically we have a complete P4(XML) request
*/
final List<RequestFieldError> errorList = rv.validateMessage(m);
if (!errorList.isEmpty()) {
for (RequestFieldError fe : errorList) {
lastWriteFuture = channel.writeAndFlush(fe.toString().concat(EOL));
}
}
/*
* Create DBHandler with message, messageStr, clientIp to get
* dbResponse
*/
InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress();
InetAddress inetaddress = socketAddress.getAddress();
String clientIp = inetaddress.getHostAddress();
/*
* I know - bad form to ask the ApplicationContext for the
* bean... BUT ...lack of time turns angels into demons
*/
final P4DbRequestHandler dbHandler = (P4DbRequestHandler) AppContext.getBean("dbRequestHandler");
// must set the requestDTO for the dbHandler!
dbHandler.setClientIp(clientIp);
dbHandler.setRequestDTO(dto);
//
// build database request and receive response (string)
String dbResponse = dbHandler.submitDbRequest();
/*
* create ResponseHandler and get back response string
*/
P4ResponseHandler responseHandler = new P4ResponseHandler(dto, dbHandler);
String responseStr = responseHandler.decodeDbServiceResponse(dbResponse);
/*
* write response string to output and repeat exercise
*/
LOG.debug("{} -- response to be written back to server:\n {}", dto.getId(), responseStr);
lastWriteFuture = channel.writeAndFlush(responseStr.concat(EOL));
//
LOG.info("{}: response sent TIMESTAMP: {}", dto.getId(), LocalDateTime.now().format(DTFormatter));
} else {
throw new P4EventException(dto.getId() + " -- Message received is not a String");
}
processWriteFutures(lastWriteFuture);
} catch (Throwable t) {
String tError = StacktraceUtil.getCustomStackTrace(t);
LOG.error(tError);
} finally {
if (lastWriteFuture != null) {
lastWriteFuture.sync();
}
}
}
private void processWriteFutures(ChannelFuture writeFuture) throws InterruptedException {
// Wait until all messages are flushed before closing the channel.
if (writeFuture != null) {
writeFuture.sync();
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
/**
* Creates a newly configured {#link ChannelPipeline} for a new channel.
*/
public class P4ListenerInitializer extends ChannelInitializer<SocketChannel> {
private static final StringDecoder DECODER = new StringDecoder();
private static final StringEncoder ENCODER = new StringEncoder();
private final SslContext sslCtx;
public P4ListenerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
#Override
public void initChannel(SocketChannel ch) {
P4ListenerHandler lh = null;
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
P4Listener.LOG.info("{} -- constructing SslContext new handler ", P4Listener.getId());
pipeline.addLast(sslCtx.newHandler(ch.alloc(), P4Listener.HOST, P4Listener.PORT));
} else {
P4Listener.LOG.info("{} -- SslContext null; bypassing adding sslCtx.newHandler(ch.alloc(), P4Listener.HOST, P4Listener.PORT) ", P4Listener.getId());
}
// Add the text line codec combination first,
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(DECODER);
P4Listener.LOG.debug("{} -- added Decoder ", P4Listener.getId());
pipeline.addLast(ENCODER);
P4Listener.LOG.debug("{} -- added Encoder ", P4Listener.getId());
// and then business logic.
pipeline.addLast(lh = new P4ListenerHandler());
P4Listener.LOG.debug("{} -- added P4ListenerHandler: {} ", P4Listener.getId(), lh.getClass().getSimpleName());
}
}
#Sharable
public class P4ListenerOutboundHandler extends ChannelOutboundHandlerAdapter {
static final Logger LOG = LoggerFactory.getLogger(P4ListenerOutboundHandler.class);
private Dto outBoundDTO = new Dto();
public String getId() {return this.outBoundDTO.getId(); }
#Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
try {
ChannelFuture lastWrite = ctx.write(Unpooled.copiedBuffer((String) msg, CharsetUtil.UTF_8));
try {
if (lastWrite != null) {
lastWrite.sync();
promise.setSuccess();
}
} catch (InterruptedException e) {
promise.setFailure(e);
e.printStackTrace();
}
} finally {
ReferenceCountUtil.release(msg);
}
}
}
output from client
Just override channelActive(...) on the handler of the server and trigger a write there.
I'm trying to build a Sling servlet that returns a modified value of a resource from the JCR. I dont want to change the original resource, so I create a SyntheticResource and make my manipulations. I then return it back using the RequestDispatcher.
The following code doesn't return the Modified content as expected and I don't see any errors in the log either. Can you tell me what I'm doing wrong here
#SlingServlet(methods = "GET", resourceTypes = "sling/components/test", selectors = "test")
public class TestServlet extends SlingSafeMethodsServlet {
/**
*
*/
private static final long serialVersionUID = 4078524820231933974L;
private final Logger log = LoggerFactory.getLogger(getClass());
#Reference
ResourceResolverFactory resolverFactory;
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
Map<String, Object> param = new HashMap<String, Object>();
ResourceResolver resolver = null;
response.setContentType("text/html");
StringWriterResponse writerResponse = new StringWriterResponse(response);
PrintWriter writer = response.getWriter();
try {
param.put(ResourceResolverFactory.SUBSERVICE, "testService");
final String path = request.getRequestPathInfo().getResourcePath();
resolver = resolverFactory.getServiceResourceResolver(param);
final Resource resource = resolver.getResource(path);
String resourceType = resource.getResourceType();
Resource testResource = new SyntheticResource(resolver,
path, resourceType) {
public <T> T adaptTo(Class<T> type) {
if (type == ValueMap.class) {
ModifiableValueMap map = resource
.adaptTo(ModifiableValueMap.class);
map.put("jcr:title", "Modified Title");
return (T)map;
}
return super.adaptTo(type);
}
};
RequestDispatcherOptions requestDispatcherOptions = new RequestDispatcherOptions();
requestDispatcherOptions.setReplaceSelectors("");
final RequestDispatcher requestDispatcher = request.getRequestDispatcher(testResource, requestDispatcherOptions);
requestDispatcher.forward(request, writerResponse);
// log.debug( writerResponse.getString() );
writer.println(writerResponse.getString());
response.setStatus(HttpServletResponse.SC_OK );
} catch (Exception e) {
log.error("Exception: ", e);
} finally {
if( resolver != null) {
resolver.close();
}
if( writer != null ){
writer.close();
}
if (writerResponse != null) {
writerResponse.clearWriter();
}
}
}
}
Using a ResourceDecorator would be simpler, it can return a ResourceWrapper that implements the required changes. Just be careful to keep the decorator's decorate method efficient when it's called for a Resource that it doesn't want to decorate, as it will be called for all Resources.