gddhy

_(:з」∠)_ 加载中...
  • 主页
  • 归档
  • 工具
  • 关于
所有文章 友链

gddhy

_(:з」∠)_ 加载中...

  • 主页
  • 归档
  • 工具
  • 关于

本地Http服务

2021-05-29
字数统计:2.7k字 阅读时长≈14分

安卓加载 assets 目录中的网页时,虽然可以用 file:///android_asset/ 实现,但是一些网页的资源却不能正常加载,解决方法建一个本地Http服务器来实现网页加载

下面直接贴代码

com\xl\webserver\WebServer.java

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
package com.xl.webserver;
import android.content.Context;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WebServer
{
/**
* 使用线程池,减少频繁创建线程的消耗。
* 线程池
*/
private ExecutorService mExecutorService = null;
private ServerSocket mServerSocket;
private int mPort = 0;
private boolean mIsAlive = true;
private Context context;

public void startServer(Context context, int port)
{
this.context = context;
mIsAlive = true;
mPort = port;
/**
* 创建缓存类型的线程池
*/
mExecutorService = Executors.newCachedThreadPool();
mExecutorService.execute(new AcceptRunnable());
}

/**
* 服务器是否还活着
* @return
*/
public boolean isAlive()
{
return mIsAlive;
}

/**
* 等到所有的线程都处理完才关闭服务器
*/
public void shutdown()
{
try {
if(mServerSocket!=null)
mServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mIsAlive = false;
if(mExecutorService!=null)
mExecutorService.shutdown();
}

/**
* 立即关闭服务器,不会等到所有的线程都处理完。
*
*/
public void shutdownNow()
{
try {
if(mServerSocket!=null)
mServerSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
mIsAlive = false;
if(mExecutorService!=null)
mExecutorService.shutdownNow();
}

/**
* 用于执行接受客户端连接的线程
* 将接受客户端的操作也放入子线程中执行,这样就不会阻塞主线程。
*/
class AcceptRunnable implements Runnable
{
@Override
public void run() {
try
{
mServerSocket = new ServerSocket(mPort);
System.out.println("Web Server startup on " + mPort);
while (mIsAlive)
{
Socket socket = mServerSocket.accept();
// 通过线程池的方式来处理客户的请求
if(mIsAlive)
mExecutorService.execute(new Processor(context, socket));
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
}

com\xl\webserver\Processor.java

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
package com.xl.webserver;

import android.content.Context;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

import folk.china.http.HTTPRequestException;
import folk.china.http.HTTPRequestHeader;

/**
* 处理一个HTTP用户请求的线程类。
*/
public class Processor implements Runnable {
private PrintStream out;
private InputStream input;
private Context context;
/**
* 默认的服务器存放访问内容的目录D:\eclipse3.4\workspace03
*/
public static String WEB_ROOT = "/mnt/sdcard/www/";

public Processor(Context context,Socket socket) {
this.context = context;
try {
input = socket.getInputStream();
out = new PrintStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}

public void run() {
try {
/*
String fileName = parse(input);
readFile(fileName);
*/
try {
HTTPRequestHeader header = HTTPRequestHeader.parse(input);
System.out.println(header.toString());
readAssetsFile(header.mLocalUrl);
} catch (HTTPRequestException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 解析客户机发过的所有HTTP请求,如果是符合HTTP协议内容的, 就分析出客户机所要访问文件的名字,并且返回文件名。
*/
public String parse(InputStream input) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(input));
String inputContent = in.readLine();
if (inputContent == null || inputContent.length() == 0) {
sendError(400, "Client invoke error");
return null;
}
// 分析客户请求的HTTP信息,分析出到底想访问哪个文件,
// 发过来的HTTP请求应该是三部分。
String request[] = inputContent.split(" ");
if (request.length != 3) {
sendError(400, "Client invoke error");
return null;
}
// 第一部分是请求的方法
String method = request[0];
// 第二部分是请求的文件名
String fileName = request[1];
// 第三部分是HTTP版本号
String httpVersion = request[2];
System.out.println("Method: " + method + ", file name: " + fileName + ", HTTP version: " + httpVersion);
return fileName;
}

/**
* 处理调用一个文件的请求
*/
public void readFile(String fileName) throws IOException {
File file_root = new File(Processor.WEB_ROOT, fileName);
String filename = null;
String content_type = "text";
int start = 0;
if ((start = fileName.indexOf('?')) > 0) filename = fileName.substring(0, start);
else filename = fileName;
File file = new File(Processor.WEB_ROOT + filename);
if (file.isDirectory()) {
file = new File(Processor.WEB_ROOT + filename, "index.html");
}
if (!file.isFile()) {
sendError(404, file.getPath() + " File Not Found");
return;
}


// 把文件的内容读取到in对象中。
InputStream in = new FileInputStream(file);
byte content[] = new byte[(int) file.length()];
in.read(content);
out.println("HTTP/1.0 200 sendFile");
if (file.getName().endsWith(".html"))
content_type = "text/html; charset=UTF-8";
else if (file.getName().endsWith(".png"))
content_type = "image/png";
out.println("Content-Type: " + content_type);

out.println("Content-length: " + content.length);
out.println();
out.write(content);
out.flush();
out.close();
in.close();
}

public void readAssetsFile(String fileName) throws IOException {
String filename = null;
String content_type = "text";
int start = 0;
if ((start = fileName.indexOf('?')) > 0) filename = fileName.substring(0, start);
else filename = fileName;
// File file = new File(Processor.WEB_ROOT + filename);
// if (file.isDirectory()) {
// file = new File(Processor.WEB_ROOT + filename, "index.html");
// }
// if (!file.isFile()) {
// sendError(404, file.getPath() + " File Not Found");
// return;
// }


// 把文件的内容读取到in对象中。
if(filename.startsWith("/")){
filename = filename.substring(1);
}
InputStream in = context.getAssets().open(Processor.WEB_ROOT + filename);
byte[] content = new byte[in.available()];
in.read(content);
out.println("HTTP/1.0 200 sendFile");
if (filename.endsWith(".html"))
content_type = "text/html; charset=UTF-8";
else if (filename.endsWith(".png"))
content_type = "image/png";
out.println("Content-Type: " + content_type);

out.println("Content-length: " + content.length);
out.println();
out.write(content);
out.flush();
out.close();
in.close();
}

/**
* 发送错误的信息
*/
public void sendError(int errNum, String errMsg) {
out.println("HTTP/1.0 " + errNum + " " + errMsg);
out.println("Content-type:text/html");
out.println();
out.println("<html>");
out.println("<meta content=&#39;text/html; charset=gb2312&#39; http-equiv=&#39;Content-Type&#39;/>");
out.println("<head><title>Error " + errNum + "--" + errMsg + "</title></head>");
out.println("<h1>" + errNum + " " + errMsg + "</h1>");
out.println("</html>");
out.println();
out.flush();
out.close();
System.out.println(errNum + ",,,," + errMsg);
}
}

folk\china\http\HTTPRequestException.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package folk.china.http;

/**
* HTTP请求异常
* @author wangchen11
*
*/
public class HTTPRequestException extends Exception {
public HTTPRequestException(String string) {
super(string);
}

private static final long serialVersionUID = 1L;
}

folk\china\http\HTTPRequestHeader.java

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
package folk.china.http;

import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import folk.china.util.ByteBuffer;

/**
* HTTP请求头
* @author wangchen11
*
*/
public class HTTPRequestHeader {
// 请求类型,是GET还是POST.
public String mRequestType = "";
// 本地路径
public String mLocalUrl = "";
// HTTP版本
public String mVersion = "";
// 所有的Get参数
public Map<String, String> mGetValues = new HashMap<String, String>();
// 所有的Post参数
public Map<String, String> mPostValues = new HashMap<String, String>();
// 所有属性集合
public Map<String, String> mProperty = new HashMap<String, String>();

public static HTTPRequestHeader parse(InputStream inputStream) throws HTTPRequestException
{
return parse(inputStream,null);
}

/**
* 解析得出HTTP请求头
* @param inputStream
* @param charset 指定参数的字符集,null:使用默认字符集
* @return 解析好的HTTP头。
* @throws HTTPRequestException 如果解析时发生异常,将抛出此异常。
*/
@SuppressWarnings("resource")
public static HTTPRequestHeader parse(InputStream inputStream, String charset) throws HTTPRequestException
{
HTTPRequestHeader header= new HTTPRequestHeader();
/**
* 不要在这里关闭这个scanner!
*/
Scanner scanner = null;
if(charset==null)
scanner = new Scanner(inputStream);
else
{
try {
scanner = new Scanner(inputStream,charset);
} catch (IllegalArgumentException e) {
throw new HTTPRequestException("unsupport charset!");
}
}

String line = null;
if(scanner.hasNextLine())
{
line = scanner.nextLine();
if(line.endsWith("\r"))
{
line = line.substring(0, line.length()-1);
}

String[]items = line.split(" ");
if(items.length!=3)
throw new HTTPRequestException("items.length!=3");
//得到请求类型
if( (!items[0].equals("GET")) && (!items[0].equals("POST")) )
throw new HTTPRequestException("Request type not 'GET' or 'POST'!");
header.mRequestType = items[0];

//分离URL和GET参数
int fristQMarkIndex = items[1].indexOf('?');
if(fristQMarkIndex==-1)
{
header.mLocalUrl = items[1];
}
else
{
header.mLocalUrl = items[1].substring(0, fristQMarkIndex);
header.mGetValues.putAll(
getParamByString( items[1].substring(fristQMarkIndex+1,items[1].length()),charset ));
}
header.mVersion = items[2];
}

/**
* 碰到空行,如果是GET请求就立即停止解析,如果是POST请求就只读下一行所有内容,下一行内容都是POST的参数。
*/
while(scanner.hasNextLine())
{
line = scanner.nextLine();
if(line.endsWith("\r"))
{
line = line.substring(0, line.length()-1);
}

if(lineIsEmpty(line))
{
if(header.mRequestType.equals("POST"))
{
if(scanner.hasNextLine())
header.mPostValues.putAll(getParamByString(scanner.nextLine(),charset));
}
break;
}

int fristColonIndex = line.indexOf(":");
if(fristColonIndex==-1)
{
throw new HTTPRequestException("line:"+line+" no expected ':'");
}
else
{
String name = line.substring(0, fristColonIndex);
String value = line.substring(fristColonIndex+1);
if(value.startsWith(" "))
value = value.substring(1);
header.mProperty.put(name,value);
}
}

return header;
}

private static Map<String, String> getParamByString(String str, String charset) throws HTTPRequestException
{
Map<String, String> map = new HashMap<String, String>();
String[]params = str.split("&");
for(String param : params)
{
int fristEqualIndex = param.indexOf('=');
if(fristEqualIndex==-1)
{
// throw new HTTPRequestException("params is not valid!");
// ignore it
continue;
}
String name = param.substring(0, fristEqualIndex);
String value = param.substring(fristEqualIndex+1);
map.put(name, getTranslatedValue(value,charset) );
}
return map;
}

/**
* 判断一行是否除了 空格 \r \t 没有其它字符了
* @param line
* @return
*/
private static boolean lineIsEmpty(String line)
{
int len = line.length();
for(int i=0;i<len;i++)
{
char ch = line.charAt(i);
switch (ch) {
case ' ':
case '\r':
case '\t':
break;
default:
return false;
}
}
return true;
}

/**
* 得到翻译后的参数
* @param value 翻译前的参数
* @param charset 字符集
* @return
* @throws HTTPRequestException
*/
private static String getTranslatedValue(String value, String charset) throws HTTPRequestException
{
StringBuilder builder = new StringBuilder();
ByteBuffer byteBuffer = new ByteBuffer();
int len = value.length();
for(int i=0;i<len;i++)
{
char ch = value.charAt(i);
if(ch=='%')
{//后跟最多两位的十六进制数
int onebyte = 0;
if(i+2>=len)
{
continue;
}
else
{
int halfByte = getHexChar(value.charAt(i+1));
onebyte = halfByte&0x0f;
if(halfByte==-1)
{
continue;
}
else
{
i++;
halfByte = getHexChar(value.charAt(i+1));
if(halfByte==-1)
{
}
else
{
i++;
onebyte = (onebyte<<4)|(halfByte&0x0f);
}
byteBuffer.put((byte)onebyte);
}

}
}
else
{
if(byteBuffer.length()>0)
{
if(charset==null)
{
builder.append(new String(byteBuffer.getBytes(), 0, byteBuffer.length()));
}
else
{
try {
builder.append(new String(byteBuffer.getBytes(), 0, byteBuffer.length(),charset));
} catch (UnsupportedEncodingException e) {
throw new HTTPRequestException("UnsupportedEncodingException:"+e.getMessage());
}
}
byteBuffer.clear();
}
builder.append(ch);
}
}

if(byteBuffer.length()>0)
{
if(charset==null)
{
builder.append(new String(byteBuffer.getBytes(), 0, byteBuffer.length()));
}
else
{
try {
builder.append(new String(byteBuffer.getBytes(), 0, byteBuffer.length(),charset));
} catch (UnsupportedEncodingException e) {
throw new HTTPRequestException("UnsupportedEncodingException:"+e.getMessage());
}
}
byteBuffer.clear();
}

return builder.toString();
}

public static int getHexChar(char ch)
{
int halfByte = -1;
if(ch>='0'&&ch<='9')
halfByte = ch-'0';
if(ch>='a'&&ch<='f')
halfByte = ch-'a'+10;
if(ch>='A'&&ch<='F')
halfByte = ch-'A'+10;
return halfByte;
}

@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(""+mRequestType+" "+mLocalUrl+"?"+mGetValues.toString()+" "+mVersion);
stringBuilder.append("\r\n");
if(mPostValues.size()>0)
{
stringBuilder.append(""+mPostValues.toString());
stringBuilder.append("\r\n");
}
stringBuilder.append(""+mProperty.toString());
stringBuilder.append("\r\n");
stringBuilder.append("\r\n");
return stringBuilder.toString();
}
}

/*
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,* /*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ga=GA1.1.1885384815.1467970825


POST / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Content-Length: 10
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,* /*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ga=GA1.1.1885384815.1467970825

name=value

(注:* /*,为了避开注释的结束符,多加了一个空格)。
*/

folk\china\util\ByteBuffer.java

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
package folk.china.util;

/**
* 可无限制增长的字节缓冲区
* @author wangchen11
*
*/
public class ByteBuffer {
//默认起始缓冲区长度
public static final int DEFAULT_START_SIZE = 64;
//默认每次增加缓冲区的长度
public static final int DEFAULT_STEP_SIZE = 512;
private byte []mBuffer = null;
private int mPos = 0;
private int mStepSize = 0;

public ByteBuffer() {
this(DEFAULT_START_SIZE);
}

public ByteBuffer(int startSize) {
this(startSize,DEFAULT_STEP_SIZE);
}

public ByteBuffer(int startSize,int stepSize) {
setRealSize(startSize);
setStepSize(stepSize);
}

public void setStepSize(int stepSize)
{
if(stepSize<1)
stepSize=1;
mStepSize = stepSize;
}

public void put(byte data)
{
checkSize(mPos+1);
mBuffer[mPos]=data;
mPos++;
}

public void put(byte []data)
{
put(data,0,data.length);
}

public void put(byte []data,int offset,int count)
{
checkSize(mPos+count);
System.arraycopy(data, offset, mBuffer, mPos, count);
mPos+=count;
}

private void checkSize(int needSize)
{
if(needSize>mBuffer.length)
{
setRealSize( needSize + mStepSize );
}
}


private void setRealSize(int realSize)
{
byte []tempBuffer = new byte[realSize];
if(mBuffer!=null)
{
System.arraycopy(mBuffer, 0, tempBuffer, 0, mPos);
}
mBuffer = tempBuffer;
}

public void clear()
{
mPos = 0;
}

public int length()
{
return mPos;
}

public byte[] getBytes()
{
return mBuffer;
}
}

需要加载assets中的网页时只需要

1
2
3
Processor.WEB_ROOT = "";
WebServer server = new WebServer();
server.startServer(this, 8080);

在WebView中加载 http://localhost:8080/index.html 即可

若需要加载文件中的网页文件需要在 Processor.java 中将 readAssetsFile() 改到 readFile() ,将 WEB_ROOT 指定为网页路径即可

在高版本安卓上还要开启允许Http访问

上面方法来自fengdeyingzi/egretAndroid

赏

谢谢你请我吃糖果

微信

扫一扫,分享到微信

微信分享二维码
安卓允许Http网络请求
安卓FTP程序
目录,不存在的…
留言已关闭
:gddhy
© gddhy
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链

tag:

  • Android
  • 软件分享
  • game
  • Hexo
  • JavaScript
  • 旧机博物馆
  • MIUI
  • Java
  • git
  • Termux
  • mtk
  • 原神
  • Win
  • Html
  • 安卓学习笔记

    缺失模块

  • Luminous' Home
  • 影子博客
  • 四次元领域
  • 初之音
  • Mr.Pumpkin
  • ZhaoQuinn 's Blog