View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.testing.nio;
29  
30  import java.io.IOException;
31  import java.net.SocketAddress;
32  import java.nio.ByteBuffer;
33  import java.nio.channels.ByteChannel;
34  import java.nio.channels.SelectionKey;
35  import java.util.concurrent.locks.Lock;
36  
37  import org.apache.hc.core5.annotation.Internal;
38  import org.apache.hc.core5.http.Chars;
39  import org.apache.hc.core5.io.CloseMode;
40  import org.apache.hc.core5.reactor.Command;
41  import org.apache.hc.core5.reactor.IOEventHandler;
42  import org.apache.hc.core5.reactor.IOSession;
43  import org.apache.hc.core5.reactor.ProtocolIOSession;
44  import org.apache.hc.core5.util.Args;
45  import org.apache.hc.core5.util.Timeout;
46  import org.slf4j.Logger;
47  
48  @Internal
49  public class LoggingIOSession implements IOSession {
50  
51      private final Logger log;
52      private final Logger wireLog;
53      private final IOSession session;
54  
55      public LoggingIOSession(final IOSession session, final Logger log, final Logger wireLog) {
56          super();
57          this.session = session;
58          this.log = log;
59          this.wireLog = wireLog;
60      }
61  
62      public LoggingIOSession(final ProtocolIOSession session, final Logger log) {
63          this(session, log, null);
64      }
65  
66      @Override
67      public String getId() {
68          return session.getId();
69      }
70  
71      @Override
72      public Lock getLock() {
73          return this.session.getLock();
74      }
75  
76      @Override
77      public void enqueue(final Command command, final Command.Priority priority) {
78          this.session.enqueue(command, priority);
79          if (this.log.isDebugEnabled()) {
80              this.log.debug("{} enqueued {} with priority {}", this.session, command.getClass().getSimpleName(), priority);
81          }
82      }
83  
84      @Override
85      public boolean hasCommands() {
86          return this.session.hasCommands();
87      }
88  
89      @Override
90      public Command poll() {
91          return this.session.poll();
92      }
93  
94      @Override
95      public ByteChannel channel() {
96          return this.session.channel();
97      }
98  
99      @Override
100     public SocketAddress getLocalAddress() {
101         return this.session.getLocalAddress();
102     }
103 
104     @Override
105     public SocketAddress getRemoteAddress() {
106         return this.session.getRemoteAddress();
107     }
108 
109     @Override
110     public int getEventMask() {
111         return this.session.getEventMask();
112     }
113 
114     private static String formatOps(final int ops) {
115         final StringBuilder buffer = new StringBuilder(6);
116         buffer.append('[');
117         if ((ops & SelectionKey.OP_READ) > 0) {
118             buffer.append('r');
119         }
120         if ((ops & SelectionKey.OP_WRITE) > 0) {
121             buffer.append('w');
122         }
123         if ((ops & SelectionKey.OP_ACCEPT) > 0) {
124             buffer.append('a');
125         }
126         if ((ops & SelectionKey.OP_CONNECT) > 0) {
127             buffer.append('c');
128         }
129         buffer.append(']');
130         return buffer.toString();
131     }
132 
133     @Override
134     public void setEventMask(final int ops) {
135         this.session.setEventMask(ops);
136         if (this.log.isDebugEnabled()) {
137             this.log.debug("{} event mask set {}", this.session, formatOps(ops));
138         }
139     }
140 
141     @Override
142     public void setEvent(final int op) {
143         this.session.setEvent(op);
144         if (this.log.isDebugEnabled()) {
145             this.log.debug("{} event set {}", this.session, formatOps(op));
146         }
147     }
148 
149     @Override
150     public void clearEvent(final int op) {
151         this.session.clearEvent(op);
152         if (this.log.isDebugEnabled()) {
153             this.log.debug("{} event cleared {}", this.session, formatOps(op));
154         }
155     }
156 
157     @Override
158     public void close() {
159         if (this.log.isDebugEnabled()) {
160             this.log.debug("{} close", this.session);
161         }
162         this.session.close();
163     }
164 
165     @Override
166     public Status getStatus() {
167         return this.session.getStatus();
168     }
169 
170     @Override
171     public boolean isOpen() {
172         return session.isOpen();
173     }
174 
175     @Override
176     public void close(final CloseMode closeMode) {
177         if (this.log.isDebugEnabled()) {
178             this.log.debug("{} shutdown {}", this.session, closeMode);
179         }
180         this.session.close(closeMode);
181     }
182 
183     @Override
184     public Timeout getSocketTimeout() {
185         return this.session.getSocketTimeout();
186     }
187 
188     @Override
189     public void setSocketTimeout(final Timeout timeout) {
190         if (this.log.isDebugEnabled()) {
191             this.log.debug("{} set timeout {}", this.session, timeout);
192         }
193         this.session.setSocketTimeout(timeout);
194     }
195 
196     @Override
197     public int read(final ByteBuffer dst) throws IOException {
198         final int bytesRead = session.read(dst);
199         if (log.isDebugEnabled()) {
200             log.debug("{} {} bytes read", session, bytesRead);
201         }
202         if (bytesRead > 0 && wireLog.isDebugEnabled()) {
203             final ByteBuffer b = dst.duplicate();
204             final int p = b.position();
205             b.limit(p);
206             b.position(p - bytesRead);
207             logData(b, "<< ");
208         }
209         return bytesRead;
210     }
211 
212     @Override
213     public int write(final ByteBuffer src) throws IOException {
214         final int byteWritten = session.write(src);
215         if (log.isDebugEnabled()) {
216             log.debug("{} {} bytes written", session, byteWritten);
217         }
218         if (byteWritten > 0 && wireLog.isDebugEnabled()) {
219             final ByteBuffer b = src.duplicate();
220             final int p = b.position();
221             b.limit(p);
222             b.position(p - byteWritten);
223             logData(b, ">> ");
224         }
225         return byteWritten;
226     }
227 
228     private void logData(final ByteBuffer data, final String prefix) throws IOException {
229         final byte[] line = new byte[16];
230         final StringBuilder buf = new StringBuilder();
231         while (data.hasRemaining()) {
232             buf.setLength(0);
233             buf.append(prefix);
234             final int chunk = Math.min(data.remaining(), line.length);
235             data.get(line, 0, chunk);
236 
237             for (int i = 0; i < chunk; i++) {
238                 final char ch = (char) line[i];
239                 if (ch > Chars.SP && ch <= Chars.DEL) {
240                     buf.append(ch);
241                 } else if (Character.isWhitespace(ch)) {
242                     buf.append(' ');
243                 } else {
244                     buf.append('.');
245                 }
246             }
247             for (int i = chunk; i < 17; i++) {
248                 buf.append(' ');
249             }
250 
251             for (int i = 0; i < chunk; i++) {
252                 buf.append(' ');
253                 final int b = line[i] & 0xff;
254                 final String s = Integer.toHexString(b);
255                 if (s.length() == 1) {
256                     buf.append("0");
257                 }
258                 buf.append(s);
259             }
260             wireLog.debug(buf.toString());
261         }
262     }
263 
264     @Override
265     public void updateReadTime() {
266         this.session.updateReadTime();
267     }
268 
269     @Override
270     public void updateWriteTime() {
271         this.session.updateWriteTime();
272     }
273 
274     @Override
275     public long getLastReadTime() {
276         return this.session.getLastReadTime();
277     }
278 
279     @Override
280     public long getLastWriteTime() {
281         return this.session.getLastWriteTime();
282     }
283 
284     @Override
285     public long getLastEventTime() {
286         return this.session.getLastEventTime();
287     }
288 
289     @Override
290     public IOEventHandler getHandler() {
291         return this.session.getHandler();
292     }
293 
294     @Override
295     public void upgrade(final IOEventHandler handler) {
296         Args.notNull(handler, "Protocol handler");
297         if (this.log.isDebugEnabled()) {
298             this.log.debug("{} protocol upgrade: {}", this.session, handler.getClass());
299         }
300         this.session.upgrade(new IOEventHandler() {
301 
302             @Override
303             public void connected(final IOSession protocolSession) throws IOException {
304                 handler.connected(protocolSession);
305             }
306 
307             @Override
308             public void inputReady(final IOSession protocolSession, final ByteBuffer src) throws IOException {
309                 if (src != null && wireLog.isDebugEnabled()) {
310                     final ByteBuffer b = src.duplicate();
311                     logData(b, "<< ");
312                 }
313                 handler.inputReady(protocolSession, src);
314             }
315 
316             @Override
317             public void outputReady(final IOSession protocolSession) throws IOException {
318                 handler.outputReady(protocolSession);
319             }
320 
321             @Override
322             public void timeout(final IOSession protocolSession, final Timeout timeout) throws IOException {
323                 handler.timeout(protocolSession, timeout);
324             }
325 
326             @Override
327             public void exception(final IOSession protocolSession, final Exception cause) {
328                 handler.exception(protocolSession, cause);
329             }
330 
331             @Override
332             public void disconnected(final IOSession protocolSession) {
333                 handler.disconnected(protocolSession);
334             }
335 
336         });
337 
338     }
339 
340     @Override
341     public String toString() {
342         return this.session.toString();
343     }
344 
345 }