客户端和服务端通讯的N种方式(四)

本文介绍使用HttpURLConnection进行文件上传及序列化对象传输的方法,包括设置请求参数、读写数据流等步骤。

HTTP是大多数应用程序中常用的与服务端交互的通讯方式。在上一篇文章中我们介绍了Ophone SDK中比较简单的一种HTTP通讯API:HttpGet和HttpPost。实际上,在Ophone SDK中还有另外一套HTTP通讯API:HttpURLConnection。这套API也可以使用在基于Java的桌面或Web应用程序中。因此,如果想设计一套通用的基于HTTP的API,建议使用HttpURLConnection。通过HTTP可以传递任何形式的数据。这要比通过基于XML的WebService更灵活,传递的数据类型更广泛。例如,可以直接通过HTTP传递二进制数据,而无需对其进行编码。

HttpURLConnection类

 java.net.HttpURLConnection类是另外一种访问HTTP资源的方式。HttpURLConnection类具有完全的访问能力,可以取代HttpGet和HttpPost类。使用HttpUrlConnection访问HTTP资源可以使用如下几步:

1.  使用java.net.URL封装HTTP资源的url,并使用openConnection方法获得HttpUrlConnection对象,代码如下:

  1. URL url = new URL("http://www.blogjava.net/nokiaguy/archive/2009/12/14/305890.html");  
  2. HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  

2.  设置请求方法,例如,GET、POST等,代码如下:

  1. httpURLConnection.setRequestMethod("POST");  

要注意的是,setRequestMethod方法的参数值必须大写,例如,GET、POST等。

3.  设置输入输出及其他权限。如果要下载HTTP资源或向服务端上传数据,需要使用如下的代码进行设置。

  1. //  下载HTTP资源,需要将setDoInput方法的参数值设为true  
  2. httpURLConnection.setDoInput(true);  
  3. //  上传数据,需要将setDoOutput方法的参数值设为true  
  4. httpURLConnection.setDoOutput(true);  

HttpURLConnection类还包含了更多的选项,例如,使用下面的代码可以禁止HttpURLConnection使用缓存。
 

  1. httpURLConnection.setUseCaches(false);  

4.  设置HTTP请求头。在很多情况下,要根据实际情况设置一些HTTP请求头,例如,下面的代码设置了Charset请求头的值为UTF-8。

  1. httpURLConnection.setRequestProperty("Charset""UTF-8");  

5.  输入和输出数据。这一步是对HTTP资源的读写操作。也就是通过InputStream和OutputStream读取和写入数据。下面的代码获得了InputStream对象和OutputStream对象。

  1. InputStream is = httpURLConnection.getInputStream();  
  2. OutputStream os = httpURLConnection.getOutputStream();  

至于是先读取还是先写入数据,需要根据具体情况而定。


6.  关闭输入输出流。虽然关闭输入输出流并不是必须的,在应用程序结束后,输入输出流会自动关闭。但显式关闭输入输出流是一个好习惯。关闭输入输出流的代码如下:

  1. Is.close();  
  2. os.close();  

上传文件

通过HttpUrlConnection可以和服务端直接进行二进制数据的交互。那么在本文给出一个上传文件的例子。通过对本例的学习,读者可以了解如何与服务端进行二进制交互。
本程序可以将手机上的文件上传到服务端。服务端程序是一个Servlet,部署完服务端程序后,启动Tomcat,在浏览器地址栏中输入如下的URL:

http://localhost:8080/upload/upload.jsp

如果在浏览器中显示如图1所示的页面,说明服务端程序已经安装成功。这个服务端程序负责接收客户端上传的文件,并将成功上传的文件保存在D:\upload目录中,如果该目录不存在,系统会自动创建该目录。读者可以使用图1所示的页面上传一个文件,观察一下效果。

图1  上传文件的页面


下面我们来实现OPhone版的文件上传客户端。浏览文件的效果如图2所示,当单击一个文件时,系统会上传该文件,上传成功后的效果如图3所示。读者可以在D:\upload目录看到上传的文件。

图2  浏览SD卡中的文件

图3 成功上传文件 


实现本例的关键是了解文件上传的原理。为了分析文件上传的原理,我们使用了HttpAnalyzer来截获图4所示的页面上传文件的HTTP请求信息。从【stream】标签页可以看到原始的HTTP请求信息,如图4所示。

图4  上传文件的HTTP请求信息


从图4可以看出,上传文件的HTTP请求信息分为如下4部分。

  • 分界符。由两部分组成:两个连字符“--”和一个任意字符串。使用浏览器上传文件一般为“-----------------数字”。分界符为单独一行。
  • 上传文件的相关信息。这些信息包括但不限于请求参数名、上传文件名、文件类型。例如,Content-Disposition: form-data; name="file"; filename="abc.jpg"。
  • 上传文件的内容。字节流形式。
  • 文件全部上传后的结束符。这个符号在图4中并没有显示出来。当上传的文件是最后一个时,在HTTP请求信息的结尾就会出现这个符号字符串。结束符和分界符类似,只是在分界符后面再加两个连字符,例如,“-----------------------------------218813199810322--”就是一个结束符。

当单击图1所示的列表中的某个文件时,会调用SD卡浏览组件的onFileItemClick事件方法,在该方法中负责上传当前单击的文件,代码如下:

  1. public void onFileItemClick(String filename)  
  2. {  
  3.     //  192.168.17.156是PC的IP地址,读者需要将这个IP换成自己机器的IP  
  4.     String uploadUrl = "http://192.168.17.82:8080/upload/UploadServlet";  
  5.     String end = "\r\n";  
  6.     String twoHyphens = "--";               //  两个连字符  
  7.     String boundary = "******";             //  分界符的字符串  
  8.     try  
  9.     {  
  10.         URL url = new URL(uploadUrl);  
  11.         HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();  
  12.         //  要想使用InputStream和OutputStream,必须使用下面两行代码  
  13.         httpURLConnection.setDoInput(true);  
  14.         httpURLConnection.setDoOutput(true);  
  15.         httpURLConnection.setUseCaches(false);  
  16.         //  设置HTTP请求方法,方法名必须大写,例如,GET、POST  
  17.         httpURLConnection.setRequestMethod("POST");  
  18.         httpURLConnection.setRequestProperty("Connection""Keep-Alive");  
  19.         httpURLConnection.setRequestProperty("Charset""UTF-8");  
  20.         //  必须在Content-Type请求头中指定分界符中的任意字符串  
  21.         httpURLConnection.setRequestProperty("Content-Type",  
  22.                 "multipart/form-data;boundary=" + boundary);  
  23.         //  获得OutputStream对象,准备上传文件  
  24.         DataOutputStream dos = new DataOutputStream(httpURLConnection.getOutputStream());  
  25.         //  设置分界符,加end表示为单独一行  
  26.         dos.writeBytes(twoHyphens + boundary + end);  
  27.         //  设置与上传文件相关的信息  
  28.         dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\""  
  29.                         + filename.substring(filename.lastIndexOf("/") + 1) + "\"" + end);  
  30.         //  在上传文件信息与文件内容之间必须有一个空行  
  31.         dos.writeBytes(end);  
  32.         //  开始上传文件    
  33.         FileInputStream fis = new FileInputStream(filename);  
  34.         byte[] buffer = new byte[8192]; // 8k  
  35.         int count = 0;  
  36.         //  读取文件内容,并写入OutputStream对象    
  37.         while ((count = fis.read(buffer)) != -1)  
  38.         {  
  39.             dos.write(buffer, 0, count);  
  40.         }  
  41.         fis.close();  
  42.         //  新起一行   
  43.         dos.writeBytes(end);  
  44.         //  设置结束符号(在分界符后面加两个连字符)  
  45.         dos.writeBytes(twoHyphens + boundary + twoHyphens + end);  
  46.         dos.flush();  
  47.         //  开始读取从服务端传过来的信息  
  48.         InputStream is = httpURLConnection.getInputStream();  
  49.         InputStreamReader isr = new InputStreamReader(is, "utf-8");  
  50.         BufferedReader br = new BufferedReader(isr);  
  51.         String result = br.readLine();  
  52.         Toast.makeText(this, result, Toast.LENGTH_LONG).show();  
  53.         dos.close();  
  54.         is.close();  
  55.     }  
  56.     catch (Exception e)  
  57.     {  
  58.     }  
  59. }  


在编写上面代码时应注意如下3点:

  • 在本例中分界符中的任意字符串使用了“******”,而不是浏览器使用的“---------------”。
  • 分界符中的任意字符串必须在Content-Type请求头中指定,好让服务端可以获得完整的分界符。
  • 在上传文件信息与上传文件内容之间必须有一个空行。

直接传输可序列化对象

我们曾经讲过,通过编码的方式传递可序列化的对象。但这是在WebService中。而且由于受到XML的限制,只能用这种方式传递二进制数据。但直接通过HTTP进行数据传输,就可以直接采用二进制的传输方式。例如,可以直接使用writeObject和readObject来发送或接受对象。当然,仍然可以采用编码的方式来传递对象,操作过程与WebService类似。而在这里我们主要介绍如何直接通过HTTP传递可序列化的对象。
如果服务端使用Java,那么最容易的方式就是编写一个Servlet。下面的Servlet负责接受一个Product对象,并在控制台输出Product对象中的属性值。

 

  1. package net.binclass;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.InputStream;  
  5. import java.io.ObjectInputStream;  
  6. import javax.servlet.ServletException;  
  7. import javax.servlet.http.HttpServlet;  
  8. import javax.servlet.http.HttpServletRequest;  
  9. import javax.servlet.http.HttpServletResponse;  
  10.   
  11. public class MyServlet extends HttpServlet  
  12. {  
  13.   
  14.     @Override  
  15.     protected void service(HttpServletRequest request,  
  16.             HttpServletResponse response) throws ServletException, IOException  
  17.     {  
  18.   
  19.         InputStream is = request.getInputStream();  
  20.   
  21.         ObjectInputStream ois = new ObjectInputStream(is);  
  22.         try  
  23.         {  
  24.             //  通过ObjectInputStream对象的readObject方法获得Product对象  
  25.             Product product = (Product) ois.readObject();  
  26.             System.out.println("product.id:" + product.getId());  
  27.             System.out.println("product.name:" + product.getName());  
  28.         }  
  29.         catch (Exception e)  
  30.         {  
  31.             System.out.println(e.getMessage());  
  32.         }  
  33.   
  34.     }  
  35.   
  36. }  

Product类的代码如下:

 

  1. package net.binclass;  
  2. import java.io.Serializable;  
  3. public class Product implements Serializable  
  4. {  
  5.     private int id;  
  6.     private String name;  
  7.   
  8.     public int getId()  
  9.     {  
  10.         return id;  
  11.     }  
  12.   
  13.     public void setId(int id)  
  14.     {  
  15.         this.id = id;  
  16.     }  
  17.   
  18.     public String getName()  
  19.     {  
  20.         return name;  
  21.     }  
  22.   
  23.     public void setName(String name)  
  24.     {  
  25.         this.name = name;  
  26.     }  
  27. }  

下面来看一下Ophone客户端的代码。

 

  1. package net.binclass;  
  2.   
  3. import java.io.ObjectOutputStream;  
  4. import java.net.HttpURLConnection;  
  5. import java.net.URL;  
  6.   
  7. import android.app.Activity;  
  8. import android.os.Bundle;  
  9. import android.view.View;  
  10.   
  11. public class Main extends Activity  
  12. {  
  13.   
  14.     @Override  
  15.     public void onCreate(Bundle savedInstanceState)  
  16.     {  
  17.         super.onCreate(savedInstanceState);  
  18.         setContentView(R.layout.main);  
  19.     }  
  20.   
  21.     public void onClick_Transmit(View view)  
  22.     {  
  23.         try  
  24.         {  
  25.             URL url = new URL("http://192.168.17.82:8080/binclass/MyServlet");  
  26.             HttpURLConnection httpURLConnection = (HttpURLConnection) url  
  27.                     .openConnection();  
  28.             // 要想使用InputStream和OutputStream,必须使用下面两行代码  
  29.   
  30.             httpURLConnection.setDoOutput(true);  
  31.   
  32.             // 设置HTTP请求方法,方法名必须大写,例如,GET、POST  
  33.             httpURLConnection.setRequestMethod("POST");  
  34.             httpURLConnection.setRequestProperty("Connection""Keep-Alive");  
  35.   
  36.             ObjectOutputStream oos = new ObjectOutputStream(httpURLConnection  
  37.                     .getOutputStream());  
  38.             Product product = new Product();  
  39.             product.setId(3456);  
  40.             product.setName("OPhone 2.0手机");  
  41.             oos.writeObject(product);  
  42.             httpURLConnection.getInputStream();  
  43.   
  44.             oos.flush();  
  45.             oos.close();  
  46.   
  47.         }  
  48.         catch (Exception e)  
  49.         {  
  50.   
  51.         }  
  52.   
  53.     }  
  54. }  


上面的代码和上传文件的代码类似。只是获得了向服务端输出对象的ObjectOutputStream对象,并使用writeObject方法直接将对象传输到了服务端。这是不是很方便呢,而无需再进行字节和编码的转换。但要注意,这种方法一般只适合于服务端和客户端都使用Java来编写的情况。单击模拟器界面上的如图5所示的按钮,就会看到图6所示的控制台中输出的Product对象的属性值。

图5  传输可序列化对象的OPhone客户端

图6  Eclipse中Tomcat的Console
 

总结


本文主要介绍了HttpUrlConnection以及如何使用HttpUrlConnection来传输二进制文件。例如,上传任意的文件,以及直接传输可序列化的对象。但要注意,即使在服务端没有返回任何数据的情况下,仍然要调用HttpUrlConnection的getInputStream方法,否则客户端不会向服务端发送请求。

<think>首先,用户的问题是基于Swagger实现HTTP客户端服务端通讯的原理、方法和实践。我需要参考提供的站内引用,但这些引用似乎Swagger无关。引用[1]提到Feign和RestTemplate,引用[2]关于限流策略,引用[3]关于消息队列,引用[4]关于高效编程。用户说这些引用仅供参考,不是真正的需求,我应该多参考用户上一次的问题和我的回答。 用户提到“请尽可能多的参考用户上一次的问题和你的回答”,但在这个对话中,这是第一条消息,所以没有上一次的问题。系统指令是第一条消息的一部分,用户查询是第二条。所以,没有历史上下文。 因此,我需要基于当前查询回答:基于Swagger实现HTTP客户端服务端通讯的原理、方法和实践。 核心主题:Swagger(现在通常称为OpenAPI),用于API文档化和HTTP通讯。 原理: - Swagger(OpenAPI)是一个规范,用于描述RESTful API。它定义了API的点、参数、响应等。 - 原理:通过定义API的规范(YAML或JSON文件),自动生成API文档,并可用于生成客户端代码和服务器存根。 - 客户端服务端通讯:基于HTTP协议。Swagger规范描述了请求方法(GET, POST等)、路径、参数、请求体、响应等。通讯时,客户端发送HTTP请求到服务端服务端处理并返回响应。 方法: - 使用Swagger Codegen或其他工具生成客户端代码和服务器代码。 - 方法包括: 1. 定义OpenAPI规范:编写YAML或JSON文件描述API。 2. 生成服务器代码:基于规范,生成服务器的框架代码(如Spring Boot控制器)。 3. 生成客户端代码:生成客户端的SDK,开发者可以直接调用客户端方法发起HTTP请求。 4. 通讯客户端使用生成的代码调用API,服务端处理请求。 实践: - 实践步骤: a. 安装Swagger工具(如Swagger Editor、Swagger UI)。 b. 定义API规范。 c. 生成代码。 d. 实现服务端逻辑。 e. 使用客户端调用API。 - 工具:Swagger Codegen、OpenAPI Generator、Springfox(用于Spring Boot集成)。 参考引用:尽管引用不直接相关,我可以尝试间接关联: - 引用[1]提到Feign,Feign是一个声明式的HTTP客户端,常Swagger结合使用。例如,在Spring Cloud中,Feign可以基于OpenAPI规范生成客户端。 - 其他引用(限流、消息队列)可能不直接相关,但可以提到在通讯中处理性能问题。 回答结构: - 清晰结构:逐步解释原理、方法、实践。 - 使用中文。 - 添加引用标识:如果引用相关部分,末尾添加[^n]。 - 生成相关问题:最后添加相关问题。 系统级指令: - 数学表达式用$...$:本回答可能不涉及数学,但如果有,要用正确格式。 - 独立公式用$$...$$:可能不需要。 - LaTeX语法正确:确保。 - 中文回答:是。 生成相关问题: - 在最后添加相关问题部分。 大纲回答: 1. 简介:什么是Swagger和其在HTTP通讯中的作用。 2. 原理:基于OpenAPI规范的通讯原理。 3. 方法:如何实现客户端和服务通讯。 4. 实践:步骤和示例。 5. 相关问题。 确保真实可靠:基于知识,Swagger是标准工具。 </think>### 基于Swagger实现HTTP客户端服务端通讯:原理、方法和实践 Swagger(现称OpenAPI Specification,OAS)是一种用于描述RESTful API的标准化规范。它允许开发者通过YAML或JSON文件定义API的接口细节(如点、请求方法、参数、响应模型等),进而自动生成API文档、客户端代码和服务框架。基于Swagger实现HTTP客户端服务端通讯的核心是**利用OpenAPI规范作为中介,自动生成通讯代码,简化HTTP请求和响应的处理流程**。下面,我将从原理、方法和实践三个方面详细解释,参考部分相关概念(如Feign客户端)来增强实用性。 #### 1. 原理 HTTP客户端服务端通讯的本质是客户端发送HTTP请求到服务端服务端处理请求并返回响应。Swagger通过以下原理简化这一过程: - **规范驱动开发**:OpenAPI规范文件(如`openapi.yaml`)定义了API的契约,包括: - HTTP方法(GET、POST等)、路径(如`/users`)、参数(查询、路径、请求体)。 - 请求和响应的数据模型(如JSON Schema)。 - 安全性(如认证头信息)。规范文件充当了“蓝图”,确保客户端和服务遵守相同的API结构[^1]。 - **代码生成**:工具(如Swagger Codegen或OpenAPI Generator)解析规范文件,自动生成: - **服务端代码**:生成框架代码(如Spring Boot控制器),开发者只需实现业务逻辑。 - **客户端代码**:生成HTTP客户端SDK(如Java、Python或TypeScript库),开发者调用SDK方法即可发起请求,无需手动处理HTTP细节。 - **通讯流程**: 1. 客户端使用生成的SDK方法(如`client.getUserById(id)`),SDK内部将方法调用转换为HTTP请求。 2. 请求通过HTTP协议发送到服务端(支持HTTPS加密确保安全)。 3. 服务端基于生成的框架接收请求,执行逻辑,并返回响应。 4. 客户端SDK解析响应,返回结构化数据(如对象或错误信息)。 这种原理降低了手动编码错误,提高了开发效率,类似于Feign的声明式风格但更标准化[^1]。 #### 2. 方法 实现基于Swagger的HTTP通讯,主要包括三个步骤:定义规范、生成代码和集成通讯。以下是具体方法: ##### (a) 定义OpenAPI规范 - **工具**:使用Swagger Editor(在线工具)或IDE插件编写YAML文件。 - **关键元素**: - `openapi`: 指定版本(如`3.0.0`)。 - `info`: 描述API元数据(如标题、版本)。 - `paths`: 定义API点(如`/api/users`)。 - `components`: 定义可复用的模型(如请求/响应体)。 - **示例YAML片段**: ```yaml openapi: 3.0.0 info: title: User API version: 1.0.0 paths: /users/{id}: get: summary: 获取用户信息 parameters: - name: id in: path required: true schema: type: integer responses: '200': description: 成功 content: application/json: schema: $ref: '#/components/schemas/User' components: schemas: User: type: object properties: id: type: integer name: type: string ``` 此文件描述了获取用户信息的GET接口,参数为路径变量`id`,返回`User`对象。 ##### (b) 生成代码 - **工具选择**: - **Swagger Codegen**:命令行工具,支持多种语言。 - **OpenAPI Generator**:更现代的替代品,社区活跃,支持30+语言。 - **生成命令示例**(使用OpenAPI Generator): - 生成服务端代码(Java Spring Boot): ```bash openapi-generator-cli generate -i openapi.yaml -g spring -o server-code ``` 这会生成控制器骨架,开发者只需填充业务逻辑。 - 生成客户端代码(TypeScript): ```bash openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o client-code ``` 生成TypeScript客户端SDK,使用Axios库处理HTTP请求。 - **集成通讯**: - **服务端**:部署生成的代码(如Spring Boot应用),添加业务逻辑(如数据库操作)。 - **客户端**:在应用中导入SDK,调用方法发起请求(示例TypeScript代码): ```typescript import { UserApi } from './client-code'; const api = new UserApi(); api.getUserById(1).then(response => { console.log(response.data); // 输出用户数据 }).catch(error => { console.error(error); }); ``` 此方法类似Feign的声明式调用,但减少了手动配置复杂度[^1]。 ##### (c) 最佳实践 - **安全性**:在规范中添加`securitySchemes`(如Bearer Token),生成代码时自动处理认证头,防止未授权访问[^1]。 - **性能优化**:结合OkHttp或Apache HttpClient提升HTTP性能(类似Feign的OkHttp集成)。 - **验证测试**:使用Swagger UI(自动生成文档界面)测试API点,确保通讯正确。 #### 3. 实践示例 以下是一个完整的实践步骤,基于Spring Boot(服务端)和TypeScript(客户端): **步骤1:编写OpenAPI规范** - 创建`openapi.yaml`文件,定义简单用户API(如上述YAML示例)。 **步骤2:生成服务端代码** - 安装OpenAPI Generator: ```bash npm install @openapitools/openapi-generator-cli -g ``` - 生成Spring Boot服务端代码: ```bash openapi-generator-cli generate -i openapi.yaml -g spring -o server ``` - 实现业务逻辑:在`server`项目中,编辑生成的`UserApiController.java`: ```java @RestController public class UserApiController implements UserApi { @Override public ResponseEntity<User> getUserById(Integer id) { User user = new User().id(id).name("John Doe"); // 示例业务逻辑 return ResponseEntity.ok(user); } } ``` **步骤3:生成客户端代码** - 生成TypeScript客户端: ```bash openapi-generator-cli generate -i openapi.yaml -g typescript-axios -o client ``` - 在客户端应用中使用(如React应用): ```typescript import { UserApi, User } from './client'; const api = new UserApi(); // 发起HTTP请求 api.getUserById(1).then((response) => { console.log('用户数据:', response.data); // 输出: {id: 1, name: "John Doe"} }); ``` **步骤4:运行测试** - 启动服务端:运行Spring Boot应用(默认口8080)。 - 调用客户端浏览器或Postman测试`http://localhost:8080/users/1`。 - 使用Swagger UI:访问`http://localhost:8080/swagger-ui.html`实时查看和测试API。 **优势局限性**: - **优势**:减少样板代码、确保API一致性、支持异步通讯和HTTPS安全。 - **局限性**:复杂业务逻辑需手动扩展;不直接处理消息队列等高并发场景(需结合其他技术如引用[3]的流量削峰策略)[^3]。 实践中,你可以集成Feign进一步简化客户端调用(如Spring Cloud OpenFeign),或添加日志/监控(类似引用[1]的Feign日志配置)[^1]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值