33、深入探索 HTML 表单与小程序的交互及调试技巧

深入探索 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&param2=value2";
    String encodedData = URLEncoder.encode(someData, "UTF-8");
    URL programURL = new URL(baseURL + "?" + encodedData);
  1. 打开连接并读取数据 :使用 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();
  1. 处理数据 :根据服务器返回的数据格式,进行相应的解析和处理。
    // 假设服务器返回的是 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;
    }
}
  1. 小程序端发送序列化对象 :将对象序列化后通过网络发送到 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();
}
  1. 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);
  1. 写入 POST 数据 :将需要发送的数据写入输出流。
    String postData = "param1=value1&param2=value2";
    OutputStream os = connection.getOutputStream();
    os.write(postData.getBytes());
    os.flush();
    os.close();
  1. 读取服务器响应 :读取服务器返回的数据并进行处理。
    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 表单和小程序在与服务器端程序交互方面各有优势。通过合理选择和运用不同的方法,可以满足各种复杂的应用需求。无论是简单的数据收集还是复杂的用户界面设计,都可以根据具体情况选择最合适的技术方案。同时,在实际开发中,要注意数据的编码、传输安全等问题,以确保系统的稳定性和可靠性。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值