View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.common.extensions;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.nio.channels.SeekableByteChannel;
25  import java.nio.file.Files;
26  import java.nio.file.Path;
27  import java.nio.file.StandardOpenOption;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  import org.eclipse.jetty.util.StringUtil;
31  import org.eclipse.jetty.util.log.Log;
32  import org.eclipse.jetty.util.log.Logger;
33  import org.eclipse.jetty.websocket.api.BatchMode;
34  import org.eclipse.jetty.websocket.api.WriteCallback;
35  import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
36  import org.eclipse.jetty.websocket.api.extensions.Frame;
37  import org.eclipse.jetty.websocket.common.Generator;
38  
39  public class FrameDebugExtension extends AbstractExtension
40  {
41      private static final Logger LOG = Log.getLogger(FrameDebugExtension.class);
42  
43      private static final int BUFSIZE = 32768;
44      private Generator generator;
45      private Path outputDir;
46      private String prefix = "frame";
47      private AtomicLong incomingId = new AtomicLong(0);
48      private AtomicLong outgoingId = new AtomicLong(0);
49  
50      @Override
51      public String getName()
52      {
53          return "@frame-debug";
54      }
55  
56      @Override
57      public void incomingFrame(Frame frame)
58      {
59          saveFrame(frame,false);
60          nextIncomingFrame(frame);
61      }
62  
63      @Override
64      public void outgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
65      {
66          saveFrame(frame,true);
67          nextOutgoingFrame(frame,callback,batchMode);
68      }
69  
70      private void saveFrame(Frame frame, boolean outgoing)
71      {
72          if (outputDir == null || generator == null)
73          {
74              return;
75          }
76  
77          StringBuilder filename = new StringBuilder();
78          filename.append(prefix);
79          if (outgoing)
80          {
81              filename.append(String.format("-outgoing-%05d",outgoingId.getAndIncrement()));
82          }
83          else
84          {
85              filename.append(String.format("-incoming-%05d",incomingId.getAndIncrement()));
86          }
87          filename.append(".dat");
88  
89          Path outputFile = outputDir.resolve(filename.toString());
90          ByteBuffer buf = getBufferPool().acquire(BUFSIZE,false);
91          try (SeekableByteChannel channel = Files.newByteChannel(outputFile,StandardOpenOption.CREATE,StandardOpenOption.WRITE))
92          {
93              generator.generateHeaderBytes(frame,buf);
94              channel.write(buf);
95              if (frame.hasPayload())
96              {
97                  channel.write(frame.getPayload().slice());
98              }
99              LOG.debug("Saved raw frame: {}",outputFile.toString());
100         }
101         catch (IOException e)
102         {
103             LOG.warn("Unable to save frame: " + filename.toString(),e);
104         }
105         finally
106         {
107             getBufferPool().release(buf);
108         }
109     }
110 
111     @Override
112     public void setConfig(ExtensionConfig config)
113     {
114         super.setConfig(config);
115 
116         String cfgOutputDir = config.getParameter("output-dir",null);
117         if (StringUtil.isNotBlank(cfgOutputDir))
118         {
119             Path path = new File(cfgOutputDir).toPath();
120             if (Files.isDirectory(path) && Files.exists(path) && Files.isWritable(path))
121             {
122                 this.outputDir = path;
123             }
124             else
125             {
126                 LOG.warn("Unable to configure {}: not a valid output directory",path.toAbsolutePath().toString());
127             }
128         }
129 
130         String cfgPrefix = config.getParameter("prefix","frame");
131         if (StringUtil.isNotBlank(cfgPrefix))
132         {
133             this.prefix = cfgPrefix;
134         }
135 
136         if (this.outputDir != null)
137         {
138             // create a non-validating, read-only generator
139             this.generator = new Generator(getPolicy(),getBufferPool(),false,true);
140         }
141     }
142 }