深入探索 HTML 表单与小程序的交互及调试技巧
1. HTML 表单控件分组
在 HTML 4.0 中,
<FIELDSET>
元素配合
<LEGEND>
元素可用于在表单中对控件进行可视化分组。不过,目前只有 Internet Explorer 支持这一功能,Netscape 4.7 版本尚不支持。所以在使用时,若要确保所有用户都能正常看到分组效果,建议仅在内部网络应用中使用,且该网络内所有用户都使用 Internet Explorer。
-
<FIELDSET>元素 :作为容器,用于包裹控件,还可选择性地包含<LEGEND>元素。它除了样式表、语言等通用属性外,没有其他特殊属性。 -
<LEGEND>元素 :只能在<FIELDSET>元素内部使用,用于在围绕控件组绘制的蚀刻边框上放置标签。其ALIGN属性用于控制标签的位置,合法值有TOP、BOTTOM、LEFT和RIGHT,默认值为TOP。在 HTML 中,使用样式表通常是控制元素对齐更好的方式,因为它允许对多个位置进行统一更改。
以下是一个示例代码
Fieldset.html
:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Grouping Controls in Internet Explorer</TITLE>
</HEAD>
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Grouping Controls in Internet Explorer</H2>
<FORM ACTION="http://localhost:8088/SomeProgram">
<FIELDSET>
<LEGEND>Group One</LEGEND>
Field 1A: <INPUT TYPE="TEXT" NAME="field1A" VALUE="Field A"><BR>
Field 1B: <INPUT TYPE="TEXT" NAME="field1B" VALUE="Field B"><BR>
Field 1C: <INPUT TYPE="TEXT" NAME="field1C" VALUE="Field C"><BR>
</FIELDSET>
<FIELDSET>
<LEGEND ALIGN="RIGHT">Group Two</LEGEND>
Field 2A: <INPUT TYPE="TEXT" NAME="field2A" VALUE="Field A"><BR>
Field 2B: <INPUT TYPE="TEXT" NAME="field2B" VALUE="Field B"><BR>
Field 2C: <INPUT TYPE="TEXT" NAME="field2C" VALUE="Field C"><BR>
</FIELDSET>
</FORM>
</BODY>
</HTML>
2. 控制标签顺序
HTML 4.0 定义了
TABINDEX
属性,可用于任何可视化 HTML 元素。其值为整数,用于控制按下
TAB
键时元素获得输入焦点的顺序。同样,目前只有 Internet Explorer 支持该属性。不过,即便页面会被多种浏览器查看,只要指定的标签顺序是为了方便用户,而非页面正常运行的必要条件,就可以使用
TABINDEX
属性。
以下是一个示例代码
Tabindex.html
:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE>Controlling TAB Order</TITLE>
</HEAD>
<BODY BGCOLOR="#FDF5E6">
<H2 ALIGN="CENTER">Controlling TAB Order</H2>
<FORM ACTION="http://localhost:8088/SomeProgram">
Field 1 (first tab selection):
<INPUT TYPE="TEXT" NAME="field1" TABINDEX=1><BR>
Field 2 (third tab selection):
<INPUT TYPE="TEXT" NAME="field2" TABINDEX=3><BR>
Field 3 (second tab selection):
<INPUT TYPE="TEXT" NAME="field3" TABINDEX=2><BR>
</FORM>
</BODY>
</HTML>
3. 调试 Web 服务器
为了帮助理解 HTML 表单的行为,这里介绍一个小型 “Web 服务器”——EchoServer。它可以读取浏览器发送的所有 HTTP 数据,并返回一个包含这些数据行的 Web 页面,这些数据行被嵌入在
<PRE>
元素中。该服务器对于调试 Servlet 也非常有用,当出现问题时,可通过它确定问题是出在数据收集方式还是数据处理方式上。
以下是其工作流程:
graph TD;
A[启动 EchoServer] --> B[监听指定端口];
B --> C[接受客户端 HTTP 请求];
C --> D[读取请求数据];
D --> E{是否为 POST 请求};
E -- 是 --> F[读取 POST 数据];
E -- 否 --> G[继续读取请求数据至空行];
F --> H[将数据嵌入 HTML 页面];
G --> H;
H --> I[返回页面给客户端];
以下是
EchoServer.java
的代码:
import java.net.*;
import java.io.*;
import java.util.StringTokenizer;
public class EchoServer extends NetworkServer {
protected int maxRequestLines = 50;
protected String serverName = "EchoServer";
public static void main(String[] args) {
int port = 8088;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException nfe) {}
}
new EchoServer(port, 0);
}
public EchoServer(int port, int maxConnections) {
super(port, maxConnections);
listen();
}
public void handleConnection(Socket server)
throws IOException{
System.out.println
(serverName + ": got connection from " +
server.getInetAddress().getHostName());
BufferedReader in = SocketUtil.getReader(server);
PrintWriter out = SocketUtil.getWriter(server);
String[] inputLines = new String[maxRequestLines];
int i;
for (i=0; i<maxRequestLines; i++) {
inputLines[i] = in.readLine();
if (inputLines[i] == null) // Client closed connection
break;
if (inputLines[i].length() == 0) { // Blank line
if (usingPost(inputLines)) {
readPostData(inputLines, i, in);
i = i + 2;
}
break;
}
}
printHeader(out);
for (int j=0; j<i; j++) {
out.println(inputLines[j]);
}
printTrailer(out);
server.close();
}
private void printHeader(PrintWriter out) {
out.println
("HTTP/1.0 200 OK\r\n" +
"Server: " + serverName + "\r\n" +
"Content-Type: text/html\r\n" +
"\r\n" +
"<!DOCTYPE HTML PUBLIC " +
"\"-//W3C//DTD HTML 4.0 Transitional//EN\">\n" +
"<HTML>\n" +
"<HEAD>\n" +
" <TITLE>" + serverName + " Results</TITLE>\n" +
"</HEAD>\n" +
"\n" +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">" + serverName +
" Results</H1>\n" +
"Here is the request line and request headers\n" +
"sent by your browser:\n" +
"<PRE>");
}
private void printTrailer(PrintWriter out) {
out.println
("</PRE>\n" +
"</BODY>\n" +
"</HTML>\n");
}
private boolean usingPost(String[] inputs) {
return(inputs[0].toUpperCase().startsWith("POST"));
}
private void readPostData(String[] inputs, int i,
BufferedReader in)
throws IOException {
int contentLength = contentLength(inputs);
char[] postData = new char[contentLength];
in.read(postData, 0, contentLength);
inputs[++i] = new String(postData, 0, contentLength);
}
private int contentLength(String[] inputs) {
String input;
for (int i=0; i<inputs.length; i++) {
if (inputs[i].length() == 0)
break;
input = inputs[i].toUpperCase();
if (input.startsWith("CONTENT-LENGTH"))
return(getLength(input));
}
return(0);
}
private int getLength(String length) {
StringTokenizer tok = new StringTokenizer(length);
tok.nextToken();
return(Integer.parseInt(tok.nextToken()));
}
}
4. 多线程版 EchoServer
当服务器需要同时接受多个客户端请求时,可使用多线程版的
ThreadedEchoServer
。
以下是
ThreadedEchoServer.java
的代码:
import java.net.*;
import java.io.*;
public class ThreadedEchoServer extends EchoServer
implements Runnable {
public static void main(String[] args) {
int port = 8088;
if (args.length > 0) {
try {
port = Integer.parseInt(args[0]);
} catch(NumberFormatException nfe) {}
}
ThreadedEchoServer echoServer =
new ThreadedEchoServer(port, 0);
echoServer.serverName = "Threaded Echo Server";
}
public ThreadedEchoServer(int port, int connections) {
super(port, connections);
}
public void handleConnection(Socket server) {
Connection connectionThread = new Connection(this, server);
connectionThread.start();
}
public void run() {
Connection currentThread =
(Connection)Thread.currentThread();
try {
super.handleConnection(currentThread.serverSocket);
} catch(IOException ioe) {
System.out.println("IOException: " + ioe);
ioe.printStackTrace();
}
}
}
class Connection extends Thread {
protected Socket serverSocket;
public Connection(Runnable serverObject,
Socket serverSocket) {
super(serverObject);
this.serverSocket = serverSocket;
}
}
5. 网络服务器基础类
为了简化网络编程,这里还提供了一些实用类,如
NetworkServer
和
SocketUtil
。
-
NetworkServer
类
:是网络服务器的基础类,需要重写
handleConnection
方法,但在很多情况下,
listen
方法可以保持不变。它使用
SocketUtil
类来简化
PrintWriter
和
BufferedReader
的创建。
-
SocketUtil
类
:提供了创建与
Socket
关联的
BufferedReader
和
PrintWriter
的快捷方式。
以下是
NetworkServer.java
的代码:
import java.net.*;
import java.io.*;
public class NetworkServer {
private int port, maxConnections;
public NetworkServer(int port, int maxConnections) {
setPort(port);
setMaxConnections(maxConnections);
}
public void listen() {
int i=0;
try {
ServerSocket listener = new ServerSocket(port);
Socket server;
while((i++ < maxConnections) || (maxConnections == 0)) {
server = listener.accept();
handleConnection(server);
}
} catch (IOException ioe) {
System.out.println("IOException: " + ioe);
ioe.printStackTrace();
}
}
protected void handleConnection(Socket server)
throws IOException{
BufferedReader in = SocketUtil.getReader(server);
PrintWriter out = SocketUtil.getWriter(server);
System.out.println
("Generic Network Server: got connection from " +
server.getInetAddress().getHostName() + "\n" +
"with first line ’" + in.readLine() + "’");
out.println("Generic Network Server");
server.close();
}
public int getMaxConnections() {
return(maxConnections);
}
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
public int getPort() {
return(port);
}
protected void setPort(int port) {
this.port = port;
}
}
以下是
SocketUtil.java
的代码:
import java.net.*;
import java.io.*;
public class SocketUtil {
public static BufferedReader getReader(Socket s)
throws IOException {
return(new BufferedReader(
new InputStreamReader(s.getInputStream())));
}
public static PrintWriter getWriter(Socket s)
throws IOException {
// 2nd argument of true means autoflush
return(new PrintWriter(s.getOutputStream(), true));
}
}
6. 小程序作为 Servlet 前端
HTML 表单虽然能简单地收集用户输入并传输到 Servlet 或 CGI 程序,但有时需要更复杂的用户界面,这时小程序就能发挥作用。小程序能让你更好地控制 GUI 控件的大小、颜色和字体,提供更多内置功能,支持自定义输入表单的开发,还能将单个用户提交的数据发送到多个服务器端程序。不过,使用 Java 编程语言设计界面通常比使用 HTML 表单需要更多的精力,尤其是当界面包含大量格式化文本时。因此,在 HTML 表单和小程序之间的选择取决于具体应用。
小程序与服务器端程序通信有三种不同的方法:
| 方法 | 描述 | 示例 |
| ---- | ---- | ---- |
| 模仿基于 GET 的 HTML 表单 | 小程序发送 GET 数据,浏览器显示结果页面 | 如
A Multisystem Search Engine Front End
示例 |
| 发送 GET 数据并在小程序内处理结果(HTTP 隧道) | 小程序发送 GET 数据到 Servlet,然后自己处理结果 | 如
A Query Viewer That Uses Object Serialization and HTTP Tunneling
示例 |
| 发送 POST 数据并在小程序内处理结果 | 小程序发送 POST 数据到 Servlet,然后自己处理结果 | 如
An Applet That Sends POST Data
示例 |
7. 使用 GET 方法发送数据并显示结果页面
可以使用
showDocument
方法让浏览器显示特定的 URL。若要从小程序发送 GET 数据,只需将数据追加到构建 URL 的字符串后面,然后创建 URL 对象并正常调用
showDocument
方法。
以下是一个基本模板:
try {
URL programURL = new URL(baseURL + "?" + someData);
getAppletContext().showDocument(programURL);
} catch(MalformedURLException mue) { ... }
需要注意的是,浏览器发送数据时会对其进行 URL 编码,即空格会转换为加号 (
+
),非字母数字字符会转换为百分号 (
%
) 后跟两位十六进制数字。因此,若小程序要与通常从 HTML 表单接收 GET 数据的服务器端程序通信,小程序需要对数据进行正确编码。JDK 1.1 中的
URLEncoder
类有一个静态的
encode
方法可以执行此编码。
8. 使用 GET 方法发送数据并在小程序内处理结果(HTTP 隧道)
在这种方式下,小程序向 Servlet 发送 GET 数据,然后自行处理返回的结果。这一过程相较于直接显示结果页面,需要更多的编程逻辑来解析和处理服务器返回的数据。
以下是一个简单的示例步骤:
1.
构建 URL
:将需要发送的数据编码后追加到服务器端程序的 URL 后面。
import java.net.*;
import java.io.*;
import java.net.URLEncoder;
try {
String baseURL = "http://example.com/servlet";
String someData = "param1=value1¶m2=value2";
String encodedData = URLEncoder.encode(someData, "UTF-8");
URL programURL = new URL(baseURL + "?" + encodedData);
-
打开连接并读取数据
:使用
URLConnection打开与服务器的连接,并读取服务器返回的数据。
URLConnection connection = programURL.openConnection();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
- 处理数据 :根据服务器返回的数据格式,进行相应的解析和处理。
// 假设服务器返回的是 JSON 数据
String jsonResponse = response.toString();
// 这里可以使用 JSON 解析库进行解析
// 例如使用 Gson 库
// Gson gson = new Gson();
// YourDataClass data = gson.fromJson(jsonResponse, YourDataClass.class);
} catch (MalformedURLException mue) {
mue.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
}
9. 使用对象序列化在小程序和 Servlet 之间交换高级数据结构
对象序列化允许将 Java 对象转换为字节流,以便在网络上传输或存储。在小程序和 Servlet 之间使用对象序列化可以方便地交换复杂的数据结构。
以下是一个简单的示例:
1.
定义可序列化的类
:确保需要传输的类实现
Serializable
接口。
import java.io.Serializable;
public class UserData implements Serializable {
private String name;
private int age;
public UserData(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
- 小程序端发送序列化对象 :将对象序列化后通过网络发送到 Servlet。
import java.io.*;
import java.net.*;
try {
UserData userData = new UserData("John", 30);
URL url = new URL("http://example.com/servlet");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
ObjectOutputStream oos = new ObjectOutputStream(connection.getOutputStream());
oos.writeObject(userData);
oos.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
- Servlet 端接收并反序列化对象 :在 Servlet 中接收字节流并将其反序列化为对象。
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class MyServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
UserData userData = (UserData) ois.readObject();
ois.close();
// 处理接收到的对象
System.out.println("Name: " + userData.getName());
System.out.println("Age: " + userData.getAge());
// 返回响应
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.println("Data received successfully");
out.close();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
}
10. 使用 POST 方法发送数据并在小程序内处理结果
当需要发送大量数据或敏感数据时,使用 POST 方法更为合适。小程序发送 POST 数据到 Servlet 后,自行处理返回的结果。
以下是一个示例步骤:
1.
构建 URLConnection 并设置请求方法为 POST
:
import java.net.*;
import java.io.*;
try {
URL url = new URL("http://example.com/servlet");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
- 写入 POST 数据 :将需要发送的数据写入输出流。
String postData = "param1=value1¶m2=value2";
OutputStream os = connection.getOutputStream();
os.write(postData.getBytes());
os.flush();
os.close();
- 读取服务器响应 :读取服务器返回的数据并进行处理。
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
// 处理服务器响应
String responseData = response.toString();
// 可以根据响应数据的格式进行相应的解析和处理
} catch (IOException ioe) {
ioe.printStackTrace();
}
11. 绕过 HTTP 服务器
在某些情况下,小程序可以绕过 HTTP 服务器,直接与运行在本地机器上的自定义服务器程序进行通信。这种方式适用于对性能要求较高或需要特殊通信协议的场景。
以下是一个简单的示例流程:
graph TD;
A[小程序] --> B[建立与自定义服务器的连接];
B --> C[发送数据到自定义服务器];
C --> D[自定义服务器处理数据];
D --> E[自定义服务器返回响应];
E --> F[小程序接收并处理响应];
在 Java 中,可以使用
Socket
类来实现与自定义服务器的通信。
import java.net.*;
import java.io.*;
try {
Socket socket = new Socket("localhost", 8888);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 发送数据
out.println("Hello, Server!");
// 接收响应
String response = in.readLine();
System.out.println("Server response: " + response);
socket.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
综上所述,HTML 表单和小程序在与服务器端程序交互方面各有优势。通过合理选择和运用不同的方法,可以满足各种复杂的应用需求。无论是简单的数据收集还是复杂的用户界面设计,都可以根据具体情况选择最合适的技术方案。同时,在实际开发中,要注意数据的编码、传输安全等问题,以确保系统的稳定性和可靠性。
超级会员免费看
1571

被折叠的 条评论
为什么被折叠?



