百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

自己的web服务器项目-request请求和response响应处理

yuyutoo 2024-10-12 01:23 3 浏览 0 评论

我们经常使用别人的服务器进行构建网站,现在我们就自己来写一个自己的服务来使用。

准备工作:下载所需的题材及文档

注:完整项目下载

一、request请求获取

1、了解request请求

在写服务器之前,我们需要知道客户端发送给我们哪些信息?以及要求我们返回哪些信息?经过测试我们能够知道用户客户端发送的信息有以下几点:

客户端发送到服务器端的请求消息,我们称之为请求(request),其实就是一个按照http协议的规则拼接而成的字符串,Request请求消息包含三部分: 请求行 消息报头 请求正文

第一部 请求行

格式:

Method Request-URI HTTP-Version CRLF

各部分分别为:

Method表示请求方法;一般为GET或者POST ;Request-URI是一个统一资源标识符; HTTP-Version表示请求的HTTP协议版本; CRLF表示回车和换行

例如:

GET /test.html HTTP/1.1

第二部 消息报头 http header

例如:

GET /test.html HTTP/1.1

Host: 127.0.0.1:9999

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,en;q=0.8,zh;q=0.5,en-US;q=0.3

Accept-Encoding: gzip, deflate

Connection: keep-alive

第三部 请求正文 http body

请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的字符串信息

注意:在第二部分header和第三部分body之间有个空行,除非没有请求正文(如果你想要亲自看到效果,请参考:浏览器中GET和POST的区别),这是因为用户在浏览网页时提交给服务器的信息是不同的

2、实现

经过以上分析,我们就能够清楚的知道,客户端发送给服务器的请求,请求信息有使用的协议、请求的方法、请求的资源路径、请求的消息报头、判断请求的内容是否为静态资源、判断请求的内容是否为动态资源、判断是否为空请求,为了使用的方便,我们需要将其封装起来,总不能使用一次读取一次吧,这样做实在是太浪费系统资源与时间了,如下代码,就是一个接口类,用于获取客户端发送过来的属性

package com.sample.http;
import java.util.Map;
 // http协议的请求
public interface HttpRequest {
 //获得请求的协议
 public String getProtocol();
 //获得请求的方法
 public String getRequestMethod();
 //获得请求的路径
 public String getRequestPath();
 //获得请求的消息报头
 public Map<String,String> getRequestHeader();
 //根据参数的名字获得请求带过来的参数值
 public String getParameter(String parameterName);
 //判断当前请求的否是静态资源
 public boolean isStaticResource();
 //判断当前请求的否是动态资源
 public boolean isDynamicResource();
 //判断当前请求的否是为空请求(有些浏览器会自动发送空请求)
 public boolean isNullRequest();
}

有了接口类之后,我们就可以创建类进行实现,下面就是实现类,用于对各个方法进行处理:

package com.sample.http;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class HttpRequestImpl implements HttpRequest{
	//客户端的Socket
	private Socket s;
	private InputStream is=null;//输入流
	private BufferedReader br=null;
	private HashMap<String,String> hmHeader=new HashMap<String,String>();//消息报头
	private HashMap<String,String> hmparameters=new HashMap<String, String>();//参数集合
	private boolean isNullRequest=false;//是否为空请求,默认false
	private String protocol=null;//请求协议
	private String requestMethod=null;//请求方法
	private String requestPath=null;//请求路径
	public HttpRequestImpl(Socket s) {
		this.s=s;
		getInfos();//调用方法
	 }
	private void getInfos()//定义一个方法,用于处理获取的客户端信息
	{
		try {
			is=s.getInputStream();
			br=new BufferedReader(new InputStreamReader(is));
			//解析第一行
			String str;
			str=br.readLine();//readLine使用回车换行判断一行是否结束
			if(str==null)
			{
				isNullRequest=true;
				return;
			}
			parseRequestMethodPathProtocol(str);//调用自己创建在本类里边的方法处理第一行信息,方法在后面
		//解析第二行---第八行
			String header=null; 
 //判断是否结束,如果结束就退出,这里的判断按较为饶人
 //首先应该明确br.readLine()的内容,当为true是对应的情况
 //也就是说当“”(中间没有空格)与br.readLine()相等时,就进入到while中
 while(!"".equals((header=br.readLine()))){
			parseRequestHeader(header);
			}
		//post和get
		if(br.ready())//post//POST提交方式判断,如果还有下一行就继续读取信息
		{
			char[] buf=new char[1024];
			int len=br.read(buf);//使用字节进行读取,因为这一行没有回车换行,readLine无法判断是否结束
			String parameter=new String(buf,0,len);
			parseRequestParamByPost(parameter);//调用自己创建在本类里边的方法处理POST方式提交的正文信息	
		}
		else
		{//get,get参数的位置在第一行连接处
			parseRequestParamByGet(requestPath);//调用自己创建在本类里边的方法处理GET方式提交的正文信息	
		}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
 //GET方法处理
 private void parseRequestParamByGet(String requestPath2) {
		String []str1=requestPath2.split("[?]");//使用“?”分割字符串
		if(str1.length==2)
		{
				parseRequestParamByPost(str1[1]);
		}
		this.requestPath=str1[0];
	}
 //POST方法处理
 private void parseRequestParamByPost(String parameter) {
		 String[] strs=parameter.split("&");
		 if(strs.length>=1)
		 {
			 for(String str:strs)
			 {
				 String [] sp=str.split("=");
 hmparameters.put(sp[0],sp[1]); 
			 }
		 }
	}
	//解析第二行到第八行
	private void parseRequestHeader(String header) throws Exception{
		String[] headHost=header.split(": ");
		 if(headHost.length!=2)
	 {
			 throw new Exception("消息报头异常,请重新提交");		 
		 }
		 hmHeader.put(headHost[0],headHost[1]);
	}
	//解析第一行
	private void parseRequestMethodPathProtocol(String str) throws Exception {
		 String[] protocolMethodPath=new String[3];//由于第一行含有三个内容,分割后需要三个String存储
		 protocolMethodPath=str.split(" ");//使用空格分割
		 if(protocolMethodPath.length==3)
		 {
		 requestMethod=protocolMethodPath[0];
		 requestPath=protocolMethodPath[1];
		 protocol=protocolMethodPath[2];
		 }
		 else
		 {
			 throw new Exception("首行参数不合适,请重新提交");
		 }
	}
	//获得请求的协议
	public String getProtocol()
	{
		return protocol;
	}
	//获得请求的方法
	public String getRequestMethod(){
		return requestMethod;
	}
	//获得请求的路径
	public String getRequestPath(){
		return requestPath;
	}
	//获得请求的消息报头
	public Map<String,String> getRequestHeader(){
		return this.hmHeader;
	}
	//根据参数的名字获得请求带过来的参数值
	public String getParameter(String parameterName){
		return hmparameters.get(parameterName);
	}
	//判断当前请求的否是静态资源
	public boolean isStaticResource(){
		return true;
	}
	//判断当前请求的否是动态资源
	public boolean isDynamicResource(){
		return true;
	}
	//判断当前请求的否是为空请求(有些浏览器会自动发送空请求)
	public boolean isNullRequest(){
 return isNullRequest;
	}
}

以上内容是对客户端(浏览器)请求内容的处理,即如何进行包装客户端请求的信息,并且将其包装成一个统一的整体,既然能够获取浏览器的内容,那么,我们就必须采取一定的措施告诉浏览器我们找到了你想要的文件,并且将返回给客户端,下面我们就来实现如何返回给客户端想要的信息

二、response响应处理

1、了解response响应

服务器在接收和解释客户端的请求消息后,服务器会返回给客户端一个HTTP响应消息,我们称之为响应(response)。其实也是一个按照http协议的规则拼接而成的一个字符串

HTTP响应也是由三个部分组成,分别是: 响应状态行、消息报头、响应正文

第一部分 响应状态行

格式如下:

HTTP-Version Status-Code Reason-Phrase CRLF

例如:

HTTP/1.1 200 OK

各部分分别为:

HTTP-Version表示服务器HTTP协议的版本;

Status-Code表示服务器发回的响应状态代码;

Reason-Phrase表示状态代码的文本描述。

CRLF表示回车和换行

第二部分 消息报头

HTTP消息报头包括普通报头、请求报头、响应报头、实体报头这四大类。

每一个 报头域 都是由 名字+冒号+空格+值 组成,消息报头域的名字不区分大小写。它们的作用是描述 客户端或者服务器 的属性

1.普通报头:即可用于请求,也可用于响应,是作为一个整体而不是特定资源与事务相关联。

2.请求报头:允许客户端传递关于自身信息和希望的响应形式。

3.响应报头:允许服务器传递关于自身信息的响应。

4.实体报头:定义被传送资源的信息。即可用于请求,也可用于响应。

什么是 MIME Type?

首先,我们要了解浏览器是如何处理内容的。在浏览器中显示的内容有 HTML、有 XML、有 GIF、还有 Flash ……那么,浏览器是如何区分它们,决定什么内容用什么形式来显示呢?答案是 MIME Type,也就是该资源的媒体类型。媒体类型通常是通过 HTTP 协议,由 Web 服务器告知浏览器的,更准确地说,是通过响应的消息报头里面的属性 Content-Type 来表示的,例如:Content-Type: text/HTML表示内容是 text/HTML 类型,也就是超文本文件。为什么是“text/HTML”而不是“HTML/text”或者别的什么?MIME Type 不是个人指定的,是经过 ietf 组织协商,以 RFC 的形式作为建议的标准发布在网上的,大多数的 Web 服务器和用户代理都会支持这个规范 (顺便说一句,Email 附件的类型也是通过 MIME Type 指定的)。

通常只有一些在互联网上获得广泛应用的格式才会获得一个 MIME Type,如果是某个客户端自己定义的格式,一般只能以 application/x- 开头。XHTML 正是一个获得广泛应用的格式,因此,在 RFC 3236 中,说明了 XHTML 格式文件的 MIME Type 应该是 application/xHTML+XML。当然,处理本地的文件,在没有人告诉浏览器某个文件的 MIME Type 的情况下,浏览器也会做一些默认的处理,这可能和你在操作系统中给文件配置的 MIME Type 有关。比如在 Windows 下,打开注册表的“HKEY_LOCAL_MACHINESOFTWAREClassesMIMEDatabaseContent Type”主键,你可以看到所有 MIME Type 的配置信息

每个MIME类型由两部分组成,前面是数据的大类别,例如声音audio、图象image等,后面定义具体的种类。

常见的MIME类型

超文本标记语言文本 .html,.html text/html

普通文本 .txt text/plain

RTF文本 .rtf application/rtf

GIF图形 .gif image/gif

JPEG图形 .ipeg,.jpg image/jpeg

au声音文件 .au audio/basic

MIDI音乐文件 mid,.midi audio/midi,audio/x-midi

RealAudio音乐文件 .ra, .ram audio/x-pn-realaudio

MPEG文件 .mpg,.mpeg video/mpeg

AVI文件 .avi video/x-msvideo

GZIP文件 .gz application/x-gzip

TAR文件 .tar application/x-tar

第三部分 响应正文

响应正文就是服务器返回的资源的内容

2、实现

首先,我们需要进行抽象,即将浏览器想要的信息,即如下内容包装起来,如下所示,我们将其包装成一个接口,在抽象时我们必须认识到用户可能会犯的错误,所以尽量使用重载的方法进行避免,在下面的接口中,使用了重载进行处理部分方法:

package com.sample.http;
import java.io.OutputStream;
import java.io.PrintWriter;
//http协议的响应
public interface HttpResponse {
	//获得一个指向客户端的字节流
	public OutputStream getOutputStream()throws Exception;
	//获得一个指向客户端的字符流
	public PrintWriter getPrintWriter()throws Exception;
	//设置响应的状态行 参数为String类型
	public void setStatusLine(String statusCode);
	//设置响应的状态行 参数为int类型
	public void setStatusLine(int statusCode);
	//设置响应消息报头
	public void setResponseHeader(String hName,String hValue);
	//设置响应消息报头中Content-Type属性
	public void setContentType(String contentType);
	//设置响应消息报头中Content-Type属性 并且同时设置编码
	public void setContentType(String contentType,String charsetName);
	//设置CRLF 回车换行 \r\n
	public void setCRLF();
	//把设置好的响应状态行、响应消息报头、固定空行这三部分写给浏览器
	public void printResponseHeader();
	//把响应正文写给浏览器
	public void printResponseContent(String requestPath);		
}

在接口中,我们能够看到详细的解释,下面我们就来实现接口中的方法:

package com.sample.http;
<pre name="code" class="java">import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
 
import com.sample.utils.ConfigUtils;
import com.sample.utils.StatusCodeUtils;
//http协议的响应
public class HttpResponseImpl implements HttpResponse {
	 //声明初始变量
	Socket s;//客户端socket
	OutputStream os;//输出端字节流
	BufferedWriter bw;//输出端字符流
	PrintWriter pw;
	StringBuffer sbuffer;//放状态行,\r\n ,
	FileInputStream fis;
	File file;
	//构造器
	public HttpResponseImpl(Socket s) {
 this.s=s;
 System.out.println("HttpRequestImpl(Socket s)");
 os=null;
 bw=null;
 pw=null;
 sbuffer=new StringBuffer();//初始化,记得,否则以下的操作会遇见空指针异常
 fis=null;
 file=null; 			
 getInfos();
	}
	 
	private void getInfos() {
			try {
				os=s.getOutputStream();
				bw=new BufferedWriter(new OutputStreamWriter(os,"GBK"));
				pw=new PrintWriter(bw);
			} catch (Exception e) {
				e.printStackTrace();
			}
	}
	//获得一个指向客户端的字节流
	public OutputStream getOutputStream()throws Exception
	{
		return os;
	}
	//获得一个指向客户端的字符流
	public PrintWriter getPrintWriter()throws Exception
	{
		return pw;
	}
	//设置响应的状态行 参数为String类型
	public void setStatusLine(String statusCode)
	{
		String str=StatusCodeUtils.getStatusCodeValue(statusCode);
		//System.out.println(str+"------"+str.length());
		sbuffer.append("HTTP/1.1 "+statusCode+" "+str);
		setCRLF();
	}
	//设置响应的状态行 参数为int类型
	public void setStatusLine(int statusCode)
	{
		setStatusLine(statusCode+"");//将int类型转化为String类型
	}
	//设置响应消息报头
	public void setResponseHeader(String hName,String hValue)
	{
		sbuffer.append(hName+": "+hValue);
		setCRLF();
	}
	//设置响应消息报头中Content-Type属性
	public void setContentType(String contentType)
	{
		setResponseHeader("Content-Type",contentType);
	}
	//设置响应消息报头中Content-Type属性 并且同时设置编码
	public void setContentType(String contentType,String charsetName)
	{//text/html;charset=utf-8
		setContentType(";charset= "+charsetName);
	}
	//设置CRLF 回车换行 \r\n
	public void setCRLF()
	{
		sbuffer.append("\r\n");
	}
	//把设置好的响应状态行、响应消息报头、固定空行这三部分写给浏览器
	public void printResponseHeader()
	{
		//设置setResponseLine,setResponseHeader,setResponseType
		String res=sbuffer.toString();
		pw.print(res);
		pw.flush();
	}
	//把响应正文写给浏览器
	public void printResponseContent(String requestPath)
	{
		//响应正文
		String getPath= requestPath;//客户请求的地址
		String webHome=(new ConfigUtils()).getConfigValue("rootPath");	
		System.out.println("配置文件中目录:"+webHome);//输出从配置文件中获取的地址
		file=new File(webHome+getPath);
		if(file.exists())//如果文件存在就执行
		{
		try {
			fis=new FileInputStream(file);
			byte[] buf=new byte[1024];
			int len=-1;
			while((len=fis.read(buf))!=-1)
			{
				//String str=buf.toString();
				//bw.write(str);//字符流写过去是一个地址,因为写过去之后需要浏览器解析,如果是图片或者其他(图片或视频是字节流)的该怎么解析呢?
				//System.out.println(str);
				os.write(buf, 0, len);
			}
			bw.flush();
			os.flush();//os要不要关???
		} catch (IOException e) {
			e.printStackTrace();
		}finally
		{
			try {
				if(bw!=null)
				bw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		}
	}
}

在Eclipse中写完以上代码我们会发现,其中有多处错误信息,其原因是我们没有进行创建以上代码所要求的类,现在我们进行创建,其使用方法请参见:java.util 类 Properties ,使用参考中的方法,我们能够进行对所需要的信息进行配置,在以上代码中使用的地方有两处,分别是【注意:这样做的好处是增减了项目的灵活性,用户能够在不查看代码的情况下随时更改配置文件等一些文件的信息,】:

(1)设置状态行处

//设置响应的状态行 参数为String类型
	public void setStatusLine(String statusCode)
	{
		String str=StatusCodeUtils.getStatusCodeValue(statusCode);
		//System.out.println(str+"------"+str.length());
		sbuffer.append("HTTP/1.1 "+statusCode+" "+str);
		setCRLF();
	}

其中StatusCodeUtils类创建如下所示,而对于status_code.properties文件存放在下载的准备文件中的/webservlet/project/中,直接复制到项目中即可:

package com.sample.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class StatusCodeUtils {
	private static Properties p;
	static 
	{
		InputStream in=null;
		p=new Properties();
		try {
			//读了xx.properties文件
			in=StatusCodeUtils.class.getResourceAsStream("status_code.properties");
			//放置到p中,即放properties文件中的key,value
			p.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally
		{
			if(in!=null)
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
	public static String getStatusCodeValue(String status)
	{ 
		return p.getProperty(status);
	}
	public static String getStatusCodeValue(int status)
	{ 
		return getStatusCodeValue(status+"");//没有空格
	}
	/*public static void main(String[] args) {//输出测试
	//Properties p=new Properties();
	// p.setProperty("rootPath","ddd");
	//System.out.println(p.get("rootPath"));
		System.out.println(getStatusCodeValue("304"));
		System.out.println(getStatusCodeValue("200"));
	}*/
}
(2)响应正文处:
//响应正文
		String getPath= requestPath;//客户请求的地址
		String webHome=(new ConfigUtils()).getConfigValue("rootPath");	
		System.out.println("配置文件中目录:"+webHome);//输出从配置文件中获取的地址
		file=new File(webHome+getPath);

在响应正文中使用了ConfigUtils类进行了项目路径的获取,代码如下所示,对于config.properties(注意:此文件中文件路径应该注意,我使用的是Linux系统,文件结构是/home/***,而对于windows系统,目录结构为:"C://webapps/*****,最好在地址栏复制地址,写到配置中")文件也在准备文件中,请自行下载,然后复制到项目中:就是下面这个东西,路径配置合适,然后你就可以将自己的项目放在webapps目录下,让自己的电脑作为服务器供其他人访问自己的网站了

ConfigUtils路径配置类,用于获取项目文件目录位置

package com.sample.utils;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
public class ConfigUtils {
	private static Properties p;
	static 
	{
		InputStream in=null;
		OutputStream on=null;
		p=new Properties();
		try {
			//读了xx.properties文件
			in=ConfigUtils.class.getResourceAsStream("config.properties");
			//放置到p中,即放properties文件中的key,value
			p.load(in);
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally
		{
			if(in!=null)
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
		}
	}
	public static String getConfigValue(String config)
	{ 
		return p.getProperty(config);
	}
	public static void main(String[] args) {//输出测试
	//	Properties p=new Properties();
	//	p.setProperty("rootPath","ddd");
	//	System.out.println(p.get("rootPath"));
		System.out.println(getConfigValue("rootPath"));
	}
}

到此为止,我们已经实现了服务器的主要任务,接受请求和处理请求,下面我们进行测试:

写一个测试类如下:

package com.sample.http;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class ServerTest {
	public static void main(String[] args) {
		//声明变量
 ServerSocket ss=null;
 Socket s=null;
 boolean flag=true;
 try {
 	int port=10002;
 	System.out.println("Server Port:"+port);
					ss=new ServerSocket(port);
					//while(flag)
					{
						//接受客户端发送过来的Socket
						s=ss.accept();
						HttpRequestImpl request=new HttpRequestImpl(s);							
						//	用于测试收到的信息
						System.out.println("获取的路径:"+request.getRequestPath());
						System.out.println("获取的:"+request.getProtocol());
						System.out.println("获取的:"+request.getRequestMethod());
						System.out.println(request.getParameter("name"));
						System.out.println(request.getParameter("id"));					
						Map<String,String> m=request.getRequestHeader();
						Set<Entry<String,String>> set=m.entrySet();
						Iterator it=set.iterator();
						while(it.hasNext())
						{
							Entry entry=(Entry<String, String> )it.next();
							System.out.println(entry.getKey()+"----"+entry.getValue());
						}
					 //写响应给浏览器
						/*
						 * 封装:
						 * */
						//输出流
						HttpResponseImpl response=new HttpResponseImpl(s);												
						response.setStatusLine(200);
						response.setContentType("text/html");
						response.printResponseHeader();
						response.setCRLF();
						response.printResponseHeader();
						response.printResponseContent(request.getRequestPath());
						//用于输出信息
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
	}
}

在浏览器中输入地址回车:http://127.0.0.1:10002/test.html?id=1212&name=suguniang ,能够看到浏览器解析后的界面,当其他电脑访问时(其他电脑指的是同一个域内的),只要将127.0.0.1修改为本地的ip地址即可

此时控制台上也输出相应的信息:

相关推荐

建筑福利-pdf转dwg格式转换器,再也不用描图-极客青年

作为一名经常熬夜画图的建筑狗或者cad用户,你体验过pdf图纸描图到cad吗?前几天一个老同学找我,说他的毕业设计需要我帮忙,发给我一份pdf图纸文件,问我怎么把pdf图纸转换成dwg格式。机智的我灵...

想学 HTML,不知从何入手?看完这篇文章你就知道了

很多人都说HTML是一门很简单的语言,看看书,看看视频就能读懂。但是,如果你完全没有接触过,就想通过看一遍教程,背背标签,想要完全了解HTML,真的有点太天真了。HTML中文...

「前端」HTML之结构

今天继续为大家分享前端的知识,如果对前端比较感兴趣的小伙伴,可以关注我,我会更大家继续分享更多与前端相关的内容,当然如果内容中又不当的或者文字错误的,欢迎大家在评论区留言,我会及时修改纠正。1.初识H...

手把手教你使用Python网络爬虫下载一本小说(附源码)

大家好,我是Python进阶者。前言前几天【磐奚鸟】大佬在群里分享了一个抓取小说的代码,感觉还是蛮不错的,这里分享给大家学习。...

用于处理pdf文件格式的转换器

在上传过程中如果单个文件太大则容易中断,而且文件太大的话对与存储也有些弊端。那么我们应该想到将文件进行压缩(注意这里压缩指的是不改变文件格式的压缩,而不是用变成压缩文件。这里就将以下用专门的软件压缩P...

乐书:在线 Kindle 电子书制作和转换工具

之前Kindle伴侣曾推荐过可以在Windows和Mac系统平台上运行的kindle电子书制作软件Sigil(教程),用它可以制作出高质量的的ePub格式电子书,当然最后还需要通...

付费文档怎么下载?教你5种方法,任意下载全网资源

网上查资料的时候,经常遇到需要注册登录或者付费的才能复制或者是下载,遇到这种情况大多数人都会选择重新查。...

捡来的知识!3种方法随便复制网页内容,白嫖真香呀

网上的资源真的多,所以许多人常常会从网上找资料。我们看到感兴趣的内容,第一时间可能会想要收入囊中。比如说截个图啊,或者挑选有意思的句子复制粘贴,记录下来。可是,有些时候,却会遇到这样的情况:1、内容不...

AI的使用,生成HTML网页。

利用deepseek,豆包,kimi以及通义千问,写入相同的需求。【写一个网页,实现抽奖功能,点击“开始”,按键显示“停止”,姓名开始显示在屏幕上,人员包括:“张三”,“里斯”,“Bool”,“流水废...

pdf转换成jpg转换器 4.1 官方正式版

pdf转换成jpg工具软件简介pdf转换成jpg转换器是一款界面简洁,操作方便的pdf转换成jpg转换器。pdf转换成jpg转换器可以将PDF文档转换为JPG,BMP,GIF,PNG,TIF图片文件。...

办公必备的office转换成pdf转换器怎么用?

2016-02-2415:53:37南方报道网评论(我要点评)字体刚从校园走出社会,对于快节奏的办公环境,难免会觉得有些吃力。在起步阶段力求将手头上的事情按时完工不出错,但是渐渐的你会发现,别人只...

为什么PDF转Word大多要收费?

PDF转Word大多都要收费?并非主要是因为技术上的难度,而是基于多方面的商业和版权考虑的,下面给大家浅分析下原因:...

如何用python生成简单的html report报告

前提:用python写了一个简单的log分析,主要也就是查询一些key,value出来,后面也可以根据需求增加。查询出来后,为了好看,搞个html表格来显示。需要的组件:jinja2flask...

学用系列|如何搞定word批量替换修改和格式转换?这里一站搞定

想必不少朋友都会碰到批量修改word文档内容、压缩文档图片、文件格式转换等重复性文档处理工作的需要,今天胖胖老师就推荐给大家一个免费工具XCLWinKits,一站搞定你所有的需要。什么是XCLWinK...

这款PDF文档转换神器,能帮你解决PDF使用中的许多难点

不管是平时的学习还是工作,相信许多朋友都经常接触PDF文件。可以说,PDF文件在我们的日常办公学习过程中的重要性和Word文档一样重要。在之前的更新中,小编介绍了几款非常不错的PDF文档格式转换软件,...

取消回复欢迎 发表评论: