java-netty-文件服务器

原本是基于netty的java文件服务器,采用http协议,后在学习web时不断升级完善中

2018.7.21:
asm工具可以用代码手动构造class文件。jadx原理就是先用smali解码dex,之后解析smali代码,用asm生成class文件,反编译成java。
javaweb中也使用asm。实现自定义语法生成class文件,可以把页面和后台代码一起写,后用程序处理访问时读取,分离,用asm生成class,动态加载,反射调用,提供服务。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
package httpbynetty;
//该服务器为下载服务器!!!!!!
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;
import javax.activation.MimetypesFileTypeMap;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.util.regex.Pattern;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.*;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
public class HttpFileServer {
private static final String DEFAULT_URL = "/";
public void run(final int port, final String url) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast("http-decoder",
new HttpRequestDecoder()); // 请求消息解码器
ch.pipeline().addLast("http-encoder",
new HttpResponseEncoder());//响应解码器
ch.pipeline().addLast("http-aggregator",
new HttpObjectAggregator(65536));// 目的是将多个消息转换为单一的request或者response对象
ch.pipeline().addLast("http-chunked",
new ChunkedWriteHandler());//目的是支持异步大文件传输()
ch.pipeline().addLast("fileServerHandler",
new HttpFileServerHandler(url));// 业务逻辑
}
});
ChannelFuture future = b.bind(port).sync();
System.out.println("HTTP文件目录服务器启动,网址是 : " + InetAddress.getLocalHost()+":"
+ port + url);
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
String url = DEFAULT_URL;
if (args.length > 1)
url = args[1];
new HttpFileServer().run(port, url);
}
}
class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private final String url;
public HttpFileServerHandler(String url)
{
this.url = url;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
/*如果无法解码400*/
// System.out.println(request);
if (!request.decoderResult().isSuccess())
{
//decoderResult解析该对象的结果
sendError(ctx, BAD_REQUEST);
return;
}
/*只支持GET方法*/
if (request.method() != GET)
{
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
final String uri = request.uri();//get的uri是什么————GET /hello.txt HTTP/1.1 需要前端知识
//会在当前网页路径下加上页面上的要求
/*格式化URL,并且获取路径*/
System.out.println(uri);//http://127.0.0.1:8080 是指host
//这里.uri得到的是http请求中的uri /bin/ GET /bin/ HTTP/1.1中间的那个(不含host)
final String path = sanitizeUri(uri);//解析uri
if (path == null)
{
sendError(ctx, FORBIDDEN);
return;
}
File file = new File(path);//file创建时最后一个有/时会被忽略掉/
// System.out.println(path);
/*如果文件不可访问或者文件不存在*/
if (file.isHidden() || !file.exists()) {
sendError(ctx, NOT_FOUND);
return;
}
/*如果是目录*/
if (file.isDirectory()) {
//1. 以/结尾就列出所有文件
if (uri.endsWith("/")) {//uri是get获取到的
sendListing(ctx, file);
//列文件
}
else
{
//2. 否则自动+/
sendRedirect(ctx, uri + '/');
//重定向到带/的
}
return;
}
if (!file.isFile()) {
sendError(ctx, FORBIDDEN);
return;
}
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(file, "r");// 以只读的方式打开文件
} catch (FileNotFoundException fnfe) {
sendError(ctx, NOT_FOUND);
return;
}
long fileLength = randomAccessFile.length();
//创建一个默认的HTTP响应
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
//设置Content Length
HttpUtil.setContentLength(response, fileLength);
//设置Content Type
setContentTypeHeader(response, file);//传入file?——自定义的函数
//如果request中有KEEP ALIVE信息
if (HttpUtil.isKeepAlive(request)) {
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
ctx.write(response);//发送回答头
ChannelFuture sendFileFuture;
//发送回答体
//通过Netty的ChunkedFile对象直接将文件写入发送到缓冲区中
//ChunkedFile是第二种,分块的方式,采用的是chunkedinput里的阻塞从文件中读取
sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0,
fileLength, 8192), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationProgressed(ChannelProgressiveFuture future,long progress, long total) {
//一个EventListener侦听器,一旦与未来相关的发送任务被传送,将被调用。 表示已经发了多少(累计) 表示总量(未知为-1)
if (total < 0) { // 总大小为止
System.err.println("Transfer progress: " + progress);
}
else {
System.err.println("Transfer progress: " + progress + " / "+ total);
}
}
@Override
public void operationComplete(ChannelProgressiveFuture future)throws Exception {
System.out.println("Transfer complete.");
}
});
//发送最后一个回答体
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
//如果不支持keep-Alive,服务器端主动关闭请求
if (!HttpUtil.isKeepAlive(request)) {
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {
cause.printStackTrace();
if (ctx.channel().isActive()) {
sendError(ctx, INTERNAL_SERVER_ERROR);
}
}
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
private String sanitizeUri(String uri) {// /bin/
try {
uri = URLDecoder.decode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
try {
uri = URLDecoder.decode(uri, "ISO-8859-1");
} catch (UnsupportedEncodingException e1) {
throw new Error();
}
}
// System.out.println(uri);// /bin/
if (!uri.startsWith(url)) {
return null;
}
if (!uri.startsWith("/")) {
return null;
}
uri = uri.replace('/', File.separatorChar);
// \bin\httpbynetty\1\
if (uri.contains(File.separator + '.')//含"/." "./" 开始为"." 结束为.
|| uri.contains('.' + File.separator) || uri.startsWith(".")
|| uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
//matches()对整个字符串进行匹配,只有整个字符串都匹配了才返回true
//此处为含<>&\之一就错
return null;
}
return System.getProperty("user.dir") + uri;//当前目录,+get的请求
// D:\javawsp\jav\netty实现http服务器\bin\httpbynetty\1\
}
private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");
private static void sendListing(ChannelHandlerContext ctx, File dir) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
StringBuilder buf = new StringBuilder();
String dirPath = dir.getPath();
buf.append("<!DOCTYPE html>\r\n");
buf.append("<html><head><title>");
buf.append(dirPath);
buf.append(" 目录:");
buf.append("</title></head><body>\r\n");
buf.append("<h3>");
buf.append(dirPath).append(" 目录:");
buf.append("</h3>\r\n");
buf.append("<img src=\"http://192.168.100.177:8080/1.jpg\" width=\"352\" height=\"220\" />");
//这里获取jpg为发送一次get请求,有俩种方式:
//1.: <img src=\"1.jpg\" width=\"352\" height=\"220\" /> get的uri直接解析为当前get的uri+src
//请求uri为:/bin/1.jpg
//2.: <img src=\"http://192.168.100.177:8080/1.jpg\" width=\"352\" height=\"220\" /> get的uri为src内写的
//请求uri为:/1.jpg
buf.append("<ul>");
buf.append("<li>链接:<a href=\"../\">..</a></li>\r\n");
for (File f : dir.listFiles()) {
if (f.isHidden() || !f.canRead()) {
continue;
}
String name = f.getName();
if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
continue;
}
buf.append("<li>链接:<a href=\"");
buf.append(name);
buf.append("/\">");
buf.append(name);
buf.append("</a></li>\r\n");
}
buf.append("</ul></body></html>\r\n");
ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
response.content().writeBytes(buffer);
buffer.release();
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);//302重定向
response.headers().set(HttpHeaderNames.LOCATION, newUri);//重定向到带/结尾的请求
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
//返回错误
private static void sendError(ChannelHandlerContext ctx,HttpResponseStatus status)
{
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
status, Unpooled.copiedBuffer("Failure: " + status.toString()
+ "\r\n", CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private static void setContentTypeHeader(HttpResponse response, File file) {
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
//把文件路径化为mime类型——MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
response.headers().set(HttpHeaderNames.CONTENT_TYPE,mimeTypesMap.getContentType(file.getPath()));
}
}