1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
139 this.generator = new Generator(getPolicy(),getBufferPool(),false,true);
140 }
141 }
142 }