则表达式是一个十分强大的工具,可以完成对字符串的模式匹配以及提取工作,在很多工程中都被广泛使用,但是我之前对于Python正则表达式模块re的使用都没有进行一些深入学习,对原始字符串之类的了解也比较浅显,之前有个同学问到我关于反斜线\在正则中的一些表现的时候我也迷惑了,跟周围人讨论后上StackOverflow问了一番,对这方面又多了一些了解,今天跟大家分享。
我们知道,反斜线\在字符串以及正则表达式中都是完成转义的工作,比如我有一个字符串s1:
现在这个字符串s1内容为123,如果我想在字符串中保留一个单引号',那么我可能的一种做法是使用双引号来包含内容,字符串s2:
Python中的单引号'和双引号'其实都可以用来指示字符串,我们在此处使用双引号而不是单引号,原因是因为如果使用单引号Python解释器将会无法对这条语句进行正常解释,因为紧跟2之后的'被用来指示字符串结束,然而后面还剩下3这个字符,所以会引发Syntax Error:
但是,只是使用语句中不包含的那种引号来指示字符串有时候是行不通的,因为有时候一个字符串可能同时包含单引号和双引号,比如字符串1"2'3,所以这时候我们需要使用反斜线\来对内容进行转义,因此对于字符串1"2'3,我们可以采用如下表示:
注意我们并没有对字符串内容中的双引号"进行转义,原因同上,字符串使用单引号指示的,因此双引号只会被认为是一个普通的字符。对于字符串内部的单引号',为了让解释器得知它是一个普通字符,我们在其前面增加了一个反斜线\,\'组合起来的意思是对反斜线后面的字符'进行转义,可以这样理解,通过添加一个反斜线\,对于\',你告诉了解释器:不要按照普通的方式去解释这个字符',这样解释器就能正确地完成对该字符串的解释。
有编程基础的读者应该会知道转义这一概念广泛存在于很多编程语言中,对\n、\r等字符应该有一定了解,类比前面的解释,\n表示:不要按照普通的方式去解释这个字符n。
进一步理解,假设我们从键盘输入了字符串:abc\nde,一共输入了7个字符,这7个字符经过Python解释器解释,两个字符\和n将会被转义成一个字符\n,请特别注意这一点,虽然从表面上看是两个字符,但其实\n是一个特殊的字符,这一点我们可以从以下代码看出:
有了这些基础,下面我们进入到正则的部分。
因为有了Python解释器首先对字符串的转义,所以正则表达式模块re看到的和你输入的可能有所不同,比如,\n在字符串层被转义成一个换行字符,然后re看到的是字符\n而不是\和n两个字符,看以下代码:
以上代码首先定义了一个前文提到的字符串s,然后使用re模块的search方法搜索,然后要搜索的字符串是\n,注意我们输入的是字符\和字符n,然后经过Python解释器的解释实际在内存中的是一个\n字符,因此re模块看到的是只有一个字符的字符串\n,而不是两个字符,最后完成搜索。
如果我们想匹配字符串\\n,应该怎么输入模式呢?,一种方法是:
对于字符串\\n,它的内容其实是一个普通的\字符和一个换行字符\n,对于我们输入的匹配模式\\n,首先经过Python解释器的字符串解释,变成了两个普通的\字符和一个换行字符\n,然后这些内容通过re模块的compile方法,compile方法发现有两个连续的\符号,因此将在接下来的匹配中匹配一个普通的\字符(请注意理解这一点),而\n字符将会匹配一个\n字符,因此该模式的匹配内容是字符串中的一个普通\字符和一个\n字符。当然,写这么多反斜线显得十分麻烦,所以Python也提供了一种方便的方式,即原始字符串,其格式为在字符串前添加一个r字符:r'content',通过这样的书写方式,Python解释器将使用不同的规则来解释转义(原始字符串不能以\符号结尾)。因此上述模式可以简化为:
让我们再回到普通字符串,考虑以下代码:
对于模式\\\n,经过Python解释器的解释将变成一个普通’`字符和一个换行字符\n,re模块的compile方法将会看到以上解释结果,然后该模式组合起来就是:匹配一个换行字符\n的转义字符,也就是对换行字符\n进行转义!结果是:
相关连接:
https://docs.python.org/3.6/library/re.html
http://stackoverflow.com/questions/33582162/backslashes-in-python-regular-expressions
本文原文地址: http://youchen.me/2017/04/24/Backslashes-in-Python-Regular-Expressions/
NET 在 System.Net.* 命名空间中提供了各种类,用于通过标准网络协议(如 HTTP 和 TCP/IP)进行通信。以下是关键组件的摘要:
本章中的 .NET 类型位于 System.Net.* 和 System.IO 命名空间中。
.NET 还提供对 FTP 的客户端支持,但只能通过已从 .NET 6 标记为过时的类。如果需要使用 FTP,最好的选择是使用 NuGet 库,例如 FluentFTP。
说明了 .NET 网络类型及其所在的通信层。大多数类型驻留在层或中。传输层定义了发送和接收字节(TCP 和 UDP)的基本协议;应用层定义了为特定应用设计的更高级别的协议,例如检索网页 (HTTP)、发送邮件 (SMTP) 以及在域名和 IP 地址 (DNS) 之间进行转换。
在应用程序层编程通常是最方便的;但是,您可能希望直接在传输层工作的原因有几个。一种是是否需要 .NET 中未提供的应用程序协议(如 POP3)来检索邮件。另一个是如果您想为特殊应用程序(如点对点客户端)发明自定义协议。
在应用程序协议中,HTTP在对通用通信的适用性方面是特殊的。它的基本操作模式(“给我包含此 URL 的网页”)很好地适应了“让我了解使用这些参数调用此终结点的结果”。(除了“get”动词之外,还有“put”,“post”和“delete”,允许基于REST的服务。
HTTP 还具有一组丰富的功能,这些功能在多层业务应用程序和面向服务的体系结构中非常有用,例如用于身份验证和加密、消息分块、可扩展标头和 Cookie 的协议,以及让许多服务器应用程序共享单个端口和 IP 地址的能力。由于这些原因,HTTP 在 .NET 中得到了很好的支持,既可以直接支持(如本章所述),也可以通过 Web API 和 ASP.NET Core 等技术在更高级别得到支持。
正如前面的讨论所表明的那样,网络是一个充斥着首字母缩略词的领域。我们在 中列出了最常见的。
网络缩略语 | ||
缩写 | 扩张 | 笔记 |
域名解析 | 域名服务 | 在域名(例如 )和 IP 地址(例如 199.54.213.2)之间进行转换 |
邮票 | 文件传输协议 | 用于发送和接收文件的基于互联网的协议 |
HTTP | 超文本传输协议 | 检索网页并运行 Web 服务 |
二世 | 互联网信息服务 | Microsoft的网络服务器软件 |
知识产权 | 网际协议 | 低于 TCP 和 UDP 的网络层协议 |
局域网 | 局域网 | 大多数局域网使用基于互联网的协议,如TCP/IP |
流行 | 邮局协议 | 检索互联网邮件 |
休息 | 再现状态转移 | 一种流行的 Web 服务体系结构,在响应中使用机器可遵循的链接,并且可以在基本 HTTP 上运行 |
短信通信 | 简单邮件传输协议 | 发送互联网邮件 |
技术合作计划(TCP | 传输和控制协议 | 传输层互联网协议,大多数更高层服务都在其上构建 |
UDP | 通用数据报协议 | 用于低开销服务(如 VoIP)的传输层互联网协议 |
北卡罗来纳大学 | 通用命名约定 | \计算机\共享名\文件名 |
乌里 | 统一资源标识符 | 无处不在的资源命名系统(例如, 或mailto:) |
网址 | 统一资源定位器 | 技术含义(从使用中淡出):URI的子集;通俗含义:URI 的同义词 |
要使通信正常工作,计算机或设备需要一个地址。互联网使用两种寻址系统:
IPv4
目前占主导地位的寻址系统;IPv4 地址的宽度为 32 位。当字符串格式时,IPv4 地址被写入为四个点分隔的小数(例如,101.102.103.104)。地址在世界上可以是唯一的,也可以在特定中是唯一的(例如在公司网络上)。
IPv6
较新的 128 位寻址系统。地址采用十六进制格式的字符串格式,带有冒号分隔符(例如,[3EA0:FFFF:198A:E4A3:4FF2:54fA:41BC:8D31])。.NET 要求在地址两边添加方括号。
System.Net 命名空间中的 IPAddress 类表示任一协议中的地址。它有一个接受字节数组的构造函数和一个接受正确格式字符串的静态 Parse 方法:
IPAddress a1 = new IPAddress (new byte[] { 101, 102, 103, 104 });
IPAddress a2 = IPAddress.Parse ("101.102.103.104");
Console.WriteLine (a1.Equals (a2)); // True
Console.WriteLine (a1.AddressFamily); // InterNetwork
IPAddress a3 = IPAddress.Parse
("[3EA0:FFFF:198A:E4A3:4FF2:54fA:41BC:8D31]");
Console.WriteLine (a3.AddressFamily); // InterNetworkV6
TCP 和 UDP 协议将每个 IP 地址分成 65,535 个端口,允许单个地址上的计算机运行多个应用程序,每个应用程序都在自己的端口上。许多应用程序具有标准的默认端口分配;例如,HTTP 使用端口 80;SMTP 使用端口 25。
从 49152 到 65535 的 TCP 和 UDP 端口是正式未分配的,因此它们非常适合测试和小规模部署。
IP 地址和端口组合在 .NET 中由 IPEndPoint 类表示:
IPAddress a = IPAddress.Parse ("101.102.103.104");
IPEndPoint ep = new IPEndPoint (a, 222); // Port 222
Console.WriteLine (ep.ToString()); // 101.102.103.104:222
防火墙阻止端口。在许多企业环境中,只有少数端口处于打开状态,通常是端口 80(用于未加密的 HTTP)和端口 443(用于安全 HTTP)。
URI 是一个特殊格式的字符串,用于描述互联网或 LAN 上的资源,例如网页、文件或电子邮件地址。示例包括 、ftp://myisp/doc.txt 和 mailto:。确切的格式由 (IETF) 定义。
URI 可以分解为一系列元素,通常是、和。System 命名空间中的 Uri 类仅执行此划分,为每个元素公开一个属性,如图 所示。
当您需要验证 URI 字符串的格式或将 URI 拆分为其组成部分时,Uri 类非常有用。否则,可以将 URI 简单地视为字符串 - 大多数网络方法都会重载以接受 Uri 对象或字符串。
可以通过将以下任何字符串传递到其构造函数中来构造 Uri 对象:
文件和 UNC 路径会自动转换为 URI:添加“file:”协议,并将反斜杠转换为正斜杠。Uri 构造函数还会在创建 Uri 之前对字符串执行一些基本的清理,包括将方案和主机名转换为小写以及删除默认和空白端口号。如果提供不带方案的 URI 字符串(如“”),则会引发 UriFormatException。
Uri 具有 IsLoopback 属性,该属性指示 Uri 是否引用本地主机(IP 地址 127.0.0.1)和一个 IsFile 属性,该属性指示 Uri 引用本地路径还是 UNC (IsUnc) 路径(对于挂载在 文件系统中的 共享,IsUnc 报告 false)。如果 IsFile 返回 true ,则 LocalPath 属性返回对本地操作系统友好的 AbsolutePath 版本(根据操作系统使用斜杠或反斜杠),您可以在该版本上调用 File.Open 。
Uri 的实例具有只读属性。要修改现有的 Uri,请实例化 UriBuilder 对象 - 该对象具有可写属性,可以通过其 Uri 属性转换回来。
Uri 还提供了比较和减去路径的方法:
Uri info = new Uri ("http://www.domain.com:80/info/");
Uri page = new Uri ("http://www.domain.com/info/page.html");
Console.WriteLine (info.Host); // www.domain.com
Console.WriteLine (info.Port); // 80
Console.WriteLine (page.Port); // 80 (Uri knows the default HTTP port)
Console.WriteLine (info.IsBaseOf (page)); // True
Uri relative = info.MakeRelativeUri (page);
Console.WriteLine (relative.IsAbsoluteUri); // False
Console.WriteLine (relative.ToString()); // page.html
相对 Uri,例如本例中的 ,如果您调用除 IsAbsoluteUri 和 ToString() 之外的几乎任何属性或方法,则会引发异常。你可以直接实例化一个相对的 Uri,如下所示:
Uri u = new Uri ("page.html", UriKind.Relative);
尾部斜杠在 URI 中很重要,如果存在路径组件,则服务器如何处理请求会有所不同。
例如,在传统的Web服务器中,给定URI ,您可以期望HTTP Web服务器在站点Web文件夹中的子目录中查找并返回默认文档(通常是)。
如果没有尾部斜杠,Web 服务器将直接在站点的根文件夹中查找一个名为 的文件(不带扩展名),这通常不是您想要的。如果不存在此类文件,大多数 Web 服务器将假定用户键入错误,并将返回 301 错误,建议客户端使用尾部斜杠重试。默认情况下,.NET HTTP 客户端将以与 Web 浏览器相同的方式透明地响应 301,方法是使用建议的 URI 重试。这意味着,如果在应该包含尾部斜杠时省略了尾部斜杠,您的请求仍然有效,但会遭受不必要的额外往返。
Uri 类还提供了静态辅助方法,例如 EscapeUriString() ,它通过将 ASCII 值大于 127 的所有字符转换为十六进制表示形式,将字符串转换为有效的 URL。CheckHostName() 和 CheckSchemeName() 方法接受字符串并检查它对于给定属性在语法上是否有效(尽管它们不尝试确定主机或 URI 是否存在)。
HttpClient 类公开了一个用于 HTTP 客户端操作的现代 API,取代了旧的 WebClient 和 WebRequest / WebResponse 类型(这些类型已被标记为过时)。
HttpClient 是为了响应基于 HTTP 的 Web API 和 REST 服务的增长而编写的,并且在处理比简单地获取网页更复杂的协议时提供了良好的体验。特别:
HttpClient 不支持进度报告。有关解决方案,请参阅 ,或通过 LINQPad 的交互式示例库。
使用 HttpClient 的最简单方法是实例化它,然后调用其 Get* 方法之一,传入一个 URI:
string html = await new HttpClient().GetStringAsync ("http://linqpad.net");
(还有GetByteArrayAsync和GetStreamAsync。HttpClient 中的所有 I/O 绑定方法都是异步的。
与其WebRequest / WebResponse的前身不同,要获得最佳性能 HttpClient ,重用相同的实例(否则DNS解析之类的事情可能会不必要地重复,并且套接字保持打开的时间超过必要的时间)。HttpClient 允许并发操作,因此以下内容是合法的,并且可以一次下载两个网页:
var client = new HttpClient();
var task1 = client.GetStringAsync ("http://www.linqpad.net");
var task2 = client.GetStringAsync ("http://www.albahari.com");
Console.WriteLine (await task1);
Console.WriteLine (await task2);
HttpClient 具有超时属性和 BaseAddress 属性,该属性为每个请求添加前缀 URI。HttpClient有点像一个薄壳:您可能希望在此处找到的大多数其他属性都是在另一个名为HttpClientHandler的类中定义的。要访问这个类,你实例化它,然后将实例传递到 HttpClient 的构造函数中:
var handler = new HttpClientHandler { UseProxy = false };
var client = new HttpClient (handler);
...
在此示例中,我们告诉处理程序禁用代理支持,这有时可以通过避免自动代理检测的成本来提高性能。还有一些属性可以控制 Cookie、自动重定向、身份验证等(我们将在以下各节以及“使用 HTTP”中介绍这些属性)。
GetStringAsync 、GetByteArrayAsync 和 GetStreamAsync 方法是调用更通用的 GetAsync 方法的便捷快捷方式,该方法返回:
var client = new HttpClient();
// The GetAsync method also accepts a CancellationToken.
HttpResponseMessage response = await client.GetAsync ("http://...");
response.EnsureSuccessStatusCode();
string html = await response.Content.ReadAsStringAsync();
HttpResponseMessage 公开了用于访问标头(请参阅和 HTTP 状态代码的属性。不成功的状态代码(如 404(未找到))不会导致引发异常,除非您显式调用 确保成功状态代码 。但是,通信或 DNS 错误确实会引发异常。
HttpContent 有一个用于写入另一个流的 CopyToAsync 方法,该方法在将输出写入文件时很有用:
using (var fileStream = File.Create ("linqpad.html"))
await response.Content.CopyToAsync (fileStream);
GetAsync 是对应于 HTTP 的四个动词的四种方法之一(其他方法是 PostAsync、PutAsync 和 DeleteAsync)。稍后我们将在“上传表单数据”中演示 PostAsync。
GetAsync 、PostAsync、PutAsync 和 DeleteAsync 都是调用 SendAsync 的快捷方式,SendAsync 是其他所有内容都馈送到的单一低级方法。要使用它,您首先构造一个 HttpRequestMessage :
var client = new HttpClient();
var request = new HttpRequestMessage (HttpMethod.Get, "http://...");
HttpResponseMessage response = await client.SendAsync (request);
response.EnsureSuccessStatusCode();
...
实例化 HttpRequestMessage 对象意味着您可以自定义请求的属性,例如标头(请参阅和内容本身,从而允许您上传数据。
实例化 HttpRequestMessage 对象后,可以通过分配其 Content 属性来上载内容。此属性的类型是一个名为 HttpContent 的抽象类。.NET 包含以下用于不同类型内容的具体子类(您也可以编写自己的子类):
例如:
var client = new HttpClient (new HttpClientHandler { UseProxy = false });
var request = new HttpRequestMessage (
HttpMethod.Post, "http://www.albahari.com/EchoPost.aspx");
request.Content = new StringContent ("This is a test");
HttpResponseMessage response = await client.SendAsync (request);
response.EnsureSuccessStatusCode();
Console.WriteLine (await response.Content.ReadAsStringAsync());
我们之前说过,大多数用于自定义请求的属性不是在 HttpClient 中定义的,而是在 HttpClientHandler 中定义的。后者实际上是抽象 HttpMessageHandler 类的子类,定义如下:
public abstract class HttpMessageHandler : IDisposable
{
protected internal abstract Task<HttpResponseMessage> SendAsync
(HttpRequestMessage request, CancellationToken cancellationToken);
public void Dispose();
protected virtual void Dispose (bool disposing);
}
SendAsync方法是从HttpClient的SendAsync方法调用的。
HttpMessageHandler非常简单,可以轻松进行子类化,并为HttpClient提供了一个扩展点。
我们可以对 HttpMessageHandler 进行子类化,创建一个处理程序来帮助进行单元测试:
class MockHandler : HttpMessageHandler
{
Func <HttpRequestMessage, HttpResponseMessage> _responseGenerator;
public MockHandler
(Func <HttpRequestMessage, HttpResponseMessage> responseGenerator)
{
_responseGenerator = responseGenerator;
}
protected override Task <HttpResponseMessage> SendAsync
(HttpRequestMessage request, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var response = _responseGenerator (request);
response.RequestMessage = request;
return Task.FromResult (response);
}
}
它的构造函数接受一个函数,该函数告诉模拟者如何从请求生成响应。这是最通用的方法,因为同一个处理程序可以测试多个请求。
SendAsync 是同步的,凭借 Task.FromResult 。我们本可以通过让我们的响应生成器返回一个 Task<HttpResponseMessage> 来保持异步性,但这是没有意义的,因为我们可以预期模拟函数运行时间很短。以下是使用我们的模拟处理程序的方法:
var mocker = new MockHandler (request =>
new HttpResponseMessage (HttpStatusCode.OK)
{
Content = new StringContent ("You asked for " + request.RequestUri)
});
var client = new HttpClient (mocker);
var response = await client.GetAsync ("http://www.linqpad.net");
string result = await response.Content.ReadAsStringAsync();
Assert.AreEqual ("You asked for http://www.linqpad.net/", result);
(Assert.AreEqual是您希望在单元测试框架(如NUnit)中找到的方法。
您可以通过子类化 De委派处理程序 来创建调用另一个消息处理程序(生成处理程序链)。您可以使用它来实现自定义身份验证、压缩和加密协议。下面演示了一个简单的日志记录处理程序:
class LoggingHandler : DelegatingHandler
{
public LoggingHandler (HttpMessageHandler nextHandler)
{
InnerHandler = nextHandler;
}
protected async override Task <HttpResponseMessage> SendAsync
(HttpRequestMessage request, CancellationToken cancellationToken)
{
Console.WriteLine ("Requesting: " + request.RequestUri);
var response = await base.SendAsync (request, cancellationToken);
Console.WriteLine ("Got response: " + response.StatusCode);
return response;
}
}
请注意,我们在覆盖 SendAsync 时保持了异步。在重写任务返回方法时引入异步修饰符是完全合法的,在这种情况下是可取的。
比写入控制台更好的解决方案是让构造函数接受某种日志记录对象。更好的办法是接受几个 Action<T> 委托,告诉它如何记录请求和响应对象。
是可以路由 HTTP 请求的中介。组织有时会将代理服务器设置为员工访问互联网的唯一方式,主要是因为它简化了安全性。代理有自己的地址,可以要求身份验证,以便只有 LAN 上的选定用户才能访问互联网。
要将代理与 HttpClient 一起使用,首先创建一个 HttpClientHandler 并分配其 Proxy 属性,然后将其馈送到 HttpClient 的构造函数中:
WebProxy p = new WebProxy ("192.178.10.49", 808);
p.Credentials = new NetworkCredential ("username", "password", "domain");
var handler = new HttpClientHandler { Proxy = p };
var client = new HttpClient (handler);
...
HttpClientHandler 还有一个 UseProxy 属性,您可以将其分配给 false,而不是清空 Proxy 属性以阻止自动检测。
如果在构造 NetworkCredential 时提供域,则使用基于 Windows 的身份验证协议。若要使用当前经过身份验证的 Windows 用户,请将静态 CredentialCache.DefaultNetworkCredentials 值分配给代理的 Credentials 属性。
作为重复设置代理的替代方法,您可以按如下方式设置全局默认值:
HttpClient.DefaultWebProxy = myWebProxy;
您可以向 HttpClient 提供用户名和密码,如下所示:
string username = "myuser";
string password = "mypassword";
var handler = new HttpClientHandler();
handler.Credentials = new NetworkCredential (username, password);
var client = new HttpClient (handler);
...
这适用于基于对话框的身份验证协议(如基本和摘要),并且可通过 AuthenticationManager 类进行扩展。它还支持 Windows NTLM 和 Kerberos(如果在构造 NetworkCredential 对象时包含域名)。如果要使用当前经过身份验证的 Windows 用户,可以将“凭据”属性保留为空,而是将“使用默认凭据”设置为 true 。
当您提供凭据时,HttpClient 会自动协商兼容的协议。在某些情况下,可以选择:例如,如果检查来自Microsoft Exchange 服务器 Web 邮件页面的初始响应,则它可能包含以下标头:
HTTP/1.1 401 Unauthorized
Content-Length: 83
Content-Type: text/html
Server: Microsoft-IIS/6.0
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="exchange.somedomain.com"
X-Powered-By: ASP.NET
Date: Sat, 05 Aug 2006 12:37:23 GMT
401 代码表示需要授权;“WWW 身份验证”标头指示理解的身份验证协议。但是,如果使用正确的用户名和密码配置 HttpClientHandler,则此消息将对你隐藏,因为运行时通过选择兼容的身份验证协议,然后使用额外的标头重新提交原始请求来自动响应。下面是一个示例:
Authorization: Negotiate TlRMTVNTUAAABAAAt5II2gjACDArAAACAwACACgAAAAQ
ATmKAAAAD0lVDRdPUksHUq9VUA==
此机制提供透明度,但会为每个请求生成额外的往返行程。通过将 HttpClientHandler 上的 PreAuthenticate 属性设置为 true 来避免对同一 URI 的后续请求进行额外的往返。
您可以使用凭据缓存对象强制使用特定的身份验证协议。凭据缓存包含一个或多个 NetworkCredential 对象,每个对象都以特定协议和 URI 前缀为密钥。例如,您可能希望在登录 Exchange Server 时避免使用基本协议,因为它以纯文本形式传输密码:
CredentialCache cache = new CredentialCache();
Uri prefix = new Uri ("http://exchange.somedomain.com");
cache.Add (prefix, "Digest", new NetworkCredential ("joe", "passwd"));
cache.Add (prefix, "Negotiate", new NetworkCredential ("joe", "passwd"));
var handler = new HttpClientHandler();
handler.Credentials = cache;
...
身份验证协议指定为字符串。有效值包括:
Basic, Digest, NTLM, Kerberos, Negotiate
在这种特殊情况下,它将选择协商,因为服务器在其身份验证标头中未指示它支持 Digest。协商是一种 Windows 协议,目前归结为 Kerberos 或 NTLM,具体取决于服务器的功能,但在部署未来安全标准时可确保应用程序的向前兼容性。
静态 CredentialCache.DefaultNetworkCredentials 属性允许您将当前经过身份验证的 Windows 用户添加到凭据缓存中,而无需指定密码:
cache.Add (prefix, "Negotiate", CredentialCache.DefaultNetworkCredentials);
另一种身份验证方法是直接设置身份验证标头:
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue ("Basic",
Convert.ToBase64String (Encoding.UTF8.GetBytes ("username:password")));
...
此策略也适用于自定义身份验证系统,如 OAuth。
HttpClient 允许您向请求添加自定义 HTTP 标头,以及在响应中枚举标头。标头只是包含元数据(如消息内容类型或服务器软件)的键/值对。HttpClient 公开具有标准 HTTP 标头属性的强类型集合。属性适用于应用于每个请求的标头:
var client = new HttpClient (handler);
client.DefaultRequestHeaders.UserAgent.Add (
new ProductInfoHeaderValue ("VisualStudio", "2022"));
client.DefaultRequestHeaders.Add ("CustomHeader", "VisualStudio/2022");
但是,类上的 Headers 属性用于特定于请求的标头。
查询字符串只是附加到带有问号的 URI 的字符串,用于将简单数据发送到服务器。可以使用以下语法在查询字符串中指定多个键/值对:
?key1=value1&key2=value2&key3=value3...
下面是一个带有查询字符串的 URI:
string requestURI = "http://www.google.com/search?q=HttpClient&hl=fr";
如果您的查询可能包含符号或空格,则可以使用 Uri 的 EscapeDataString 方法创建一个合法的 URI:
string search = Uri.EscapeDataString ("(HttpClient or HttpRequestMessage)");
string language = Uri.EscapeDataString ("fr");
string requestURI = "http://www.google.com/search?q=" + search +
"&hl=" + language;
此生成的 URI 为:
http://www.google.com/search?q=(HttpClient%20OR%20HttpRequestMessage)&hl=fr
(EscapeDataString 与 EscapeUriString 类似,不同之处在于它还对 & 和 = 等字符进行编码,否则会弄乱查询字符串。
若要上载 HTML 表单数据,请创建并填充 FormUrlEncodedContent 对象。然后,可以将其传递到 PostAsync 方法中,也可以将其分配给请求的 Content 属性:
string uri = "http://www.albahari.com/EchoPost.aspx";
var client = new HttpClient();
var dict = new Dictionary<string,string>
{
{ "Name", "Joe Albahari" },
{ "Company", "O'Reilly" }
};
var values = new FormUrlEncodedContent (dict);
var response = await client.PostAsync (uri, values);
response.EnsureSuccessStatusCode();
Console.WriteLine (await response.Content.ReadAsStringAsync());
Cookie 是 HTTP 服务器在响应标头中发送到客户端的名称/值字符串对。Web 浏览器客户端通常会记住 Cookie,并在每次后续请求(到同一地址)中将它们重播到服务器,直到它们到期。Cookie 允许服务器知道它是在与一分钟前还是昨天的同一客户端通信,而无需在 URI 中提供混乱的查询字符串。
默认情况下,HttpClient 会忽略从服务器接收的任何 Cookie。要接受 cookie,请创建一个 CookieContainer 对象并为其分配一个 HttpClientHandler:
var cc = new CookieContainer();
var handler = new HttpClientHandler();
handler.CookieContainer = cc;
var client = new HttpClient (handler);
...
要在将来的请求中重播收到的 Cookie,只需再次使用相同的 CookieContainer 对象即可。或者,您可以从新的 CookieContainer 开始,然后手动添加 cookie,如下所示:
Cookie c = new Cookie ("PREF",
"ID=6b10df1da493a9c4:TM=1179...",
"/",
".google.com");
freshCookieContainer.Add (c);
第三个和第四个参数指示发起方的路径和域。客户端上的 CookieContainer 可以容纳来自许多不同位置的 Cookie;HttpClient 仅发送路径和域与服务器路径和域匹配的 cookie。
如果需要在 .NET 6 中编写 HTTP 服务器,另一种更高级别的方法是使用最小 API ASP.NET。以下是入门所需的全部内容:
var app = WebApplication.CreateBuilder().Build();
app.MapGet ("/", () => "Hello, world!");
app.Run();
您可以使用 HttpListener 类编写自己的 .NET HTTP 服务器。下面是一个简单的服务器,它侦听端口 51111,等待单个客户端请求,然后返回一行回复:
using var server = new SimpleHttpServer();
// Make a client request:
Console.WriteLine (await new HttpClient().GetStringAsync
("http://localhost:51111/MyApp/Request.txt"));
class SimpleHttpServer : IDisposable
{
readonly HttpListener listener = new HttpListener();
public SimpleHttpServer() => ListenAsync();
async void ListenAsync()
{
listener.Prefixes.Add ("http://localhost:51111/MyApp/"); // Listen on
listener.Start(); // port 51111
// Await a client request:
HttpListenerContext context = await listener.GetContextAsync();
// Respond to the request:
string msg = "You asked for: " + context.Request.RawUrl;
context.Response.ContentLength64 = Encoding.UTF8.GetByteCount (msg);
context.Response.StatusCode = (int)HttpStatusCode.OK;
using (Stream s = context.Response.OutputStream)
using (StreamWriter writer = new StreamWriter (s))
await writer.WriteAsync (msg);
}
public void Dispose() => listener.Close();
}
OUTPUT: You asked for: /MyApp/Request.txt
在Windows上,HttpListener在内部不使用.NET Socket对象;相反,它调用Windows HTTP Server API。这允许计算机上的许多应用程序侦听相同的 IP 地址和端口,只要每个应用程序注册不同的地址前缀即可。在我们的示例中,我们注册了前缀 http://localhost/myapp,因此另一个应用程序可以自由侦听另一个前缀(如 http://localhost/anotherapp)上的同一 IP 和端口。这是有价值的,因为在公司防火墙上打开新端口在政治上可能很困难。
当您调用 GetContext 时,HttpListener 会等待下一个客户端请求,返回具有请求和响应属性的对象。每个都类似于客户端请求或响应,但从服务器的角度来看。例如,您可以读取和写入标头和 Cookie 到请求和响应对象,就像在客户端一样。
您可以根据预期的客户端受众选择完全支持 HTTP 协议功能的程度。至少应设置每个请求的内容长度和状态代码。
这是一个非常简单的网页服务器,
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
class WebServer
{
HttpListener _listener;
string _baseFolder; // Your web page folder.
public WebServer (string uriPrefix, string baseFolder)
{
_listener = new HttpListener();
_listener.Prefixes.Add (uriPrefix);
_baseFolder = baseFolder;
}
public async void Start()
{
_listener.Start();
while (true)
try
{
var context = await _listener.GetContextAsync();
Task.Run (() => ProcessRequestAsync (context));
}
catch (HttpListenerException) { break; } // Listener stopped.
catch (InvalidOperationException) { break; } // Listener stopped.
}
public void Stop() => _listener.Stop();
async void ProcessRequestAsync (HttpListenerContext context)
{
try
{
string filename = Path.GetFileName (context.Request.RawUrl);
string path = Path.Combine (_baseFolder, filename);
byte[] msg;
if (!File.Exists (path))
{
Console.WriteLine ("Resource not found: " + path);
context.Response.StatusCode = (int) HttpStatusCode.NotFound;
msg = Encoding.UTF8.GetBytes ("Sorry, that page does not exist");
}
else
{
context.Response.StatusCode = (int) HttpStatusCode.OK;
msg = File.ReadAllBytes (path);
}
context.Response.ContentLength64 = msg.Length;
using (Stream s = context.Response.OutputStream)
await s.WriteAsync (msg, 0, msg.Length);
}
catch (Exception ex) { Console.WriteLine ("Request error: " + ex); }
}
}
以下代码启动了操作:
// Listen on port 51111, serving files in d:\webroot:
var server = new WebServer ("http://localhost:51111/", @"d:\webroot");
try
{
server.Start();
Console.WriteLine ("Server running... press Enter to stop");
Console.ReadLine();
}
finally { server.Stop(); }
您可以使用任何 Web 浏览器在客户端对此进行测试;在这种情况下,URI 将 http://localhost:51111/ 加上网页的名称。
如果其他软件竞争同一端口,HttpListener 将不会启动(除非该软件也使用 Windows HTTP Server API)。可能侦听默认端口 80 的应用程序示例包括 Web 服务器或对等程序(如 Skype)。
我们对异步函数的使用使该服务器具有可扩展性和效率。但是,从用户界面 (UI) 线程开始会阻碍可伸缩性,因为对于每个,执行会在每次等待后反弹回 UI 线程。鉴于我们没有共享状态,产生这样的开销特别没有意义,因此在 UI 场景中,我们会像这样离开 UI 线程。
Task.Run (Start);
或者在调用 GetContextAsync 后调用 ConfigureAwait(false)。
请注意,我们使用 Task.Run 来调用 ProcessRequestAsync,即使该方法已经是异步的。这允许调用方处理另一个请求,而不必首先等待方法的同步阶段(直到第一个等待)。
静态 Dns 类封装 DNS,该 DNS 在原始 IP 地址(如 66.135.192.87)和人类友好域名(如 )之间进行转换。
方法从域名转换为 IP 地址(或多个地址):
foreach (IPAddress a in Dns.GetHostAddresses ("albahari.com"))
Console.WriteLine (a.ToString()); // 205.210.42.167
GetHostEntry 方法则相反,从地址转换为域名:
IPHostEntry entry = Dns.GetHostEntry ("205.210.42.167");
Console.WriteLine (entry.HostName); // albahari.com
GetHostEntry 还接受 IPAddress 对象,因此您可以将 IP 地址指定为字节数组:
IPAddress address = new IPAddress (new byte[] { 205, 210, 42, 167 });
IPHostEntry entry = Dns.GetHostEntry (address);
Console.WriteLine (entry.HostName); // albahari.com
当您使用 WebRequest 或 TcpClient 等类时,域名会自动解析为 IP 地址。但是,如果您计划在应用程序的生命周期内向同一地址发出许多网络请求,则有时可以通过首先使用 Dns 将域名显式转换为 IP 地址,然后从该点开始直接与 IP 地址通信来提高性能。这避免了重复往返解析相同的域名,并且在传输层(通过 TcpClient 、UdpClient 或 Socket )处理时可能会有所帮助。
DNS 类还提供可等待的基于任务的异步方法:
foreach (IPAddress a in await Dns.GetHostAddressesAsync ("albahari.com"))
Console.WriteLine (a.ToString());
命名空间中的 SmtpClient 类允许您通过无处不在的简单邮件传输协议 (SMTP) 发送邮件。要发送简单的文本消息,请实例化 SmtpClient ,将其 Host 属性设置为 SMTP 服务器地址,然后调用 发送 :
SmtpClient client = new SmtpClient();
client.Host = "mail.myserver.com";
client.Send ("from@adomain.com", "to@adomain.com", "subject", "body");
构造 MailMessage 对象会公开更多选项,包括添加附件的功能:
SmtpClient client = new SmtpClient();
client.Host = "mail.myisp.net";
MailMessage mm = new MailMessage();
mm.Sender = new MailAddress ("kay@domain.com", "Kay");
mm.From = new MailAddress ("kay@domain.com", "Kay");
mm.To.Add (new MailAddress ("bob@domain.com", "Bob"));
mm.CC.Add (new MailAddress ("dan@domain.com", "Dan"));
mm.Subject = "Hello!";
mm.Body = "Hi there. Here's the photo!";
mm.IsBodyHtml = false;
mm.Priority = MailPriority.High;
Attachment a = new Attachment ("photo.jpg",
System.Net.Mime.MediaTypeNames.Image.Jpeg);
mm.Attachments.Add (a);
client.Send (mm);
为了阻止垃圾邮件发送者,互联网上的大多数SMTP服务器仅接受来自经过身份验证的连接的连接,并要求通过SSL进行通信。
var client = new SmtpClient ("smtp.myisp.com", 587)
{
Credentials = new NetworkCredential ("me@myisp.com", "MySecurePass"),
EnableSsl = true
};
client.Send ("me@myisp.com", "someone@somewhere.com", "Subject", "Body");
Console.WriteLine ("Sent");
通过更改 DeliveryMethod 属性,可以指示 SmtpClient 改用 IIS 发送邮件,或者只是将每封邮件写入指定目录中的 文件。这在开发过程中可能很有用。
SmtpClient client = new SmtpClient();
client.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
client.PickupDirectoryLocation = @"c:\mail";
TCP 和 UDP 构成了传输层协议,大多数互联网和 LAN 服务都在其上构建。HTTP(版本 2 及更低版本)、FTP 和 SMTP 使用 TCP;DNS 和 HTTP 版本 3 使用 UDP。TCP 是面向连接的,包括可靠性机制;UDP 是无连接的,开销较低,并支持广播。使用UDP,IP语音(VoIP)也是如此。
与较高层相比,传输层提供了更大的灵活性,并可能提高性能,但它要求您自己处理身份验证和加密等任务。
使用 .NET 中的 TCP,您可以选择更易于使用的 TcpClient 和 TcpListener 外观类,也可以选择功能丰富的 Socket 类。 (实际上,您可以混合搭配,因为 TcpClient 通过 Client 属性公开基础套接字对象。Socket 类公开了更多的配置选项,并允许直接访问网络层 (IP) 和非基于 Internet 的协议,例如 Novell 的 SPX/IPX。
与其他协议一样,TCP 区分客户端和服务器:客户端发起请求,而服务器等待请求。下面是同步 TCP 客户端请求的基本结构:
using (TcpClient client = new TcpClient())
{
client.Connect ("address", port);
using (NetworkStream n = client.GetStream())
{
// Read and write to the network stream...
}
}
TcpClient 的连接方法会阻塞,直到建立连接(ConnectAsync 是异步等价物)。然后,NetworkStream提供了一种双向通信方式,用于从服务器发送和接收字节的数据。
一个简单的TCP服务器如下所示:
TcpListener listener = new TcpListener (<ip address>, port);
listener.Start();
while (keepProcessingRequests)
using (TcpClient c = listener.AcceptTcpClient())
using (NetworkStream n = c.GetStream())
{
// Read and write to the network stream...
}
listener.Stop();
TcpListener 需要侦听的本地 IP 地址(例如,具有两个网卡的计算机可以有两个地址)。您可以使用 IPAddress.Any 指示它侦听所有(或唯一)本地 IP 地址。AcceptTcpClient 阻塞,直到收到客户端请求(同样,还有一个异步版本),此时我们调用 GetStream ,就像在客户端一样。
在传输层工作时,您需要决定谁何时通话以及通话多长时间的协议,就像使用对讲机一样。如果双方同时交谈或倾听,沟通就会中断!
让我们发明一个协议,在这个协议中,客户端首先说“你好”,然后服务器通过说“你好马上回来!代码如下:
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
new Thread (Server).Start(); // Run server method concurrently.
Thread.Sleep (500); // Give server time to start.
Client();
void Client()
{
using (TcpClient client = new TcpClient ("localhost", 51111))
using (NetworkStream n = client.GetStream())
{
BinaryWriter w = new BinaryWriter (n);
w.Write ("Hello");
w.Flush();
Console.WriteLine (new BinaryReader (n).ReadString());
}
}
void Server() // Handles a single client request, then exits.
{
TcpListener listener = new TcpListener (IPAddress.Any, 51111);
listener.Start();
using (TcpClient c = listener.AcceptTcpClient())
using (NetworkStream n = c.GetStream())
{
string msg = new BinaryReader (n).ReadString();
BinaryWriter w = new BinaryWriter (n);
w.Write (msg + " right back!");
w.Flush(); // Must call Flush because we're not
} // disposing the writer.
listener.Stop();
}
// OUTPUT: Hello right back!
在此示例中,我们使用 localhost 环回在同一台计算机上运行客户端和服务器。我们任意选择了未分配范围内的端口(高于 49152),并使用 BinaryWriter 和 BinaryReader 对文本消息进行编码。我们避免关闭或处置读取器和编写器,以便在我们的对话完成之前保持底层 NetworkStream 打开。
BinaryReader 和 BinaryWriter 似乎是读取和写入字符串的奇怪选择。但是,它们比StreamReader和StreamWriter有一个主要优势:它们在字符串前面加上一个指示长度的整数,因此BinaryReader总是确切地知道要读取多少字节。如果你调用StreamReader.ReadToEnd,你可能会无限期地阻止,因为NetworkStream没有终点!只要连接处于打开状态,网络流就永远无法确定客户端不会发送更多数据。
StreamReader实际上完全超出了NetworkStream的界限,即使你只打算调用ReadLine。这是因为 StreamReader 具有预读缓冲区,这可能导致它读取的字节数超过当前可用的字节数,从而无限期阻塞(或直到套接字超时)。其他流(如 FileStream)不会遭受与 StreamReader 的这种不兼容,因为它们有一个明确的 — 此时 Read 立即返回值 0 。
TcpClient 和 TcpListener 提供基于任务的异步方法,以实现可扩展的并发性。使用这些只是将阻止方法调用替换为其 *Async 版本并等待返回的任务的问题。
在下面的示例中,我们编写了一个异步 TCP 服务器,该服务器接受长度为 5,000 字节的请求,反转字节,然后将其发送回客户端:
async void RunServerAsync ()
{
var listener = new TcpListener (IPAddress.Any, 51111);
listener.Start ();
try
{
while (true)
Accept (await listener.AcceptTcpClientAsync ());
}
finally { listener.Stop(); }
}
async Task Accept (TcpClient client)
{
await Task.Yield ();
try
{
using (client)
using (NetworkStream n = client.GetStream ())
{
byte[] data = new byte [5000];
int bytesRead = 0; int chunkSize = 1;
while (bytesRead < data.Length && chunkSize > 0)
bytesRead += chunkSize =
await n.ReadAsync (data, bytesRead, data.Length - bytesRead);
Array.Reverse (data); // Reverse the byte sequence
await n.WriteAsync (data, 0, data.Length);
}
}
catch (Exception ex) { Console.WriteLine (ex.Message); }
}
这样的程序是可扩展的,因为它不会在请求期间阻塞线程。因此,如果 1,000 个客户端通过慢速网络连接同时连接(例如,每个请求从开始到结束需要几秒钟),则该程序在这段时间内不需要 1,000 个线程(与同步解决方案不同)。相反,它仅在 await 表达式之前和之后执行代码所需的短时间内租用线程。
.NET 不提供对 POP3 的应用程序层支持,因此您需要在 TCP 层写入才能从 POP3 服务器接收邮件。幸运的是,这是一个简单的协议;POP3 对话是这样的:
客户 | 邮件服务器 | 笔记 |
客户端连接... | +好的 你好。 | 欢迎辞 |
用户乔 | +确定 需要密码。 | |
通票密码 | +确定已登录。 | |
列表 | +OK 1 1876 2 5412 3 845 . | 列出服务器上每封邮件的 ID 和文件大小 |
RETR 1 | +OK 1876 八位字节 . | 检索具有指定 ID 的邮件 |
德勒 1 | +确定已删除。 | 从服务器中删除邮件 |
退出 | +好的再见。 |
每个命令和响应都由换行符 (CR + LF) 终止,但多行 LIST 和 RETR 命令除外,它们由单独行上的单个点终止。因为我们不能将 StreamReader 与 网络流 ,我们可以从编写一个辅助方法开始,以非缓冲方式读取一行文本:
string ReadLine (Stream s)
{
List<byte> lineBuffer = new List<byte>();
while (true)
{
int b = s.ReadByte();
if (b == 10 || b < 0) break;
if (b != 13) lineBuffer.Add ((byte)b);
}
return Encoding.UTF8.GetString (lineBuffer.ToArray());
}
我们还需要一个帮助程序方法来发送命令。因为我们总是期望收到以 +OK 开头的响应,所以我们可以同时读取和验证响应:
void SendCommand (Stream stream, string line)
{
byte[] data = Encoding.UTF8.GetBytes (line + "\r\n");
stream.Write (data, 0, data.Length);
string response = ReadLine (stream);
if (!response.StartsWith ("+OK"))
throw new Exception ("POP Error: " + response);
}
编写这些方法后,检索邮件的工作很容易。我们在端口 110(默认 POP3 端口)上建立 TCP 连接,然后开始与服务器通信。在此示例中,我们将每封邮件写入扩展名为 的随机命名文件,然后再从服务器中删除邮件:
using (TcpClient client = new TcpClient ("mail.isp.com", 110))
using (NetworkStream n = client.GetStream())
{
ReadLine (n); // Read the welcome message.
SendCommand (n, "USER username");
SendCommand (n, "PASS password");
SendCommand (n, "LIST"); // Retrieve message IDs
List<int> messageIDs = new List<int>();
while (true)
{
string line = ReadLine (n); // e.g., "1 1876"
if (line == ".") break;
messageIDs.Add (int.Parse (line.Split (' ')[0] )); // Message ID
}
foreach (int id in messageIDs) // Retrieve each message.
{
SendCommand (n, "RETR " + id);
string randomFile = Guid.NewGuid().ToString() + ".eml";
using (StreamWriter writer = File.CreateText (randomFile))
while (true)
{
string line = ReadLine (n); // Read next line of message.
if (line == ".") break; // Single dot = end of message.
if (line == "..") line = "."; // "Escape out" double dot.
writer.WriteLine (line); // Write to output file.
}
SendCommand (n, "DELE " + id); // Delete message off server.
}
SendCommand (n, "QUIT");
}
可以在 NuGet 上找到开源 POP3 库,这些库为协议方面提供支持,例如身份验证 TLS/SSL 连接、MIME 分析等。
者:hetu666
来源:CSDN
HTML(HyperText Markup Language) 不是一门编程语言,而是一种用来告知浏览器如何组织页面的标记语言。
HTML 可复杂、可简单,一切取决于开发者。它由一系列的元素组成,这些元素可以用来包围不同部分的内容,使其以某种方式呈现或者工作。 一对标签可以为一段文字或者一张图片添加超链接,将文字设置为斜体,改变字号,等等
元素的主要部分有:
1开始标签(Opening tag):包含元素的名称(本例为 p),被左、右角括号所包围。表示元素从这里开始或者开始起作用 —— 在本例中即段落由此开始。
2结束标签(Closing tag):与开始标签相似,只是其在元素名之前包含了一个斜杠。这表示着元素的结尾 —— 在本例中即段落在此结束。初学者常常会犯忘记包含结束标签的错误,这可能会产生一些奇怪的结果。
3内容(Content):元素的内容,本例中就是所输入的文本本身。
4元素(Element):开始标签、结束标签与内容相结合,便是一个完整的元素。
块级元素在页面中以块的形式展现 —— 相对于其前面的内容它会出现在新的一行,其后的内容也会被挤到下一行展现。块级元素通常用于展示页面上结构化的内容,例如段落、列表、导航菜单、页脚等等。一个以block形式展现的块级元素不会被嵌套进内联元素中,但可以嵌套在其它块级元素中。
<p>第四</p><p>第五</p><p>第六</p>
效果:
一个属性必须包含如下内容:
1,在元素和属性之间有个空格space (如果已经有一个或多个属性,就与前一个属性之间有一个空格.)
2属性后面紧跟着一个“=”符号.
3,有一个属性值,由一对引号“ ”引起来.
有时你会看到没有值的属性,它是合法的。这些属性被称为布尔属性,他们只能有跟它的属性名一样的属性值。
<!-- 使用disabled属性来防止终端用户输入文本到输入框中 --> <input type="text" disabled> <!-- 下面这个输入框没有disabled属性,所以用户可以向其中输入 --> <input type="text">
在目前为止,本章内容所有的属性都是由双引号来包裹。也许在一些HTML中,你以前也见过单引号。这只是风格的问题,你可以从中选择一个你喜欢的。以下两种情况都可以:
<a href="http://www.example.com">示例站点链接</a> <a href='http://www.example.com'>示例站点链接</a>
但你应该注意单引号和双引号不能在一个属性值里面混用。下面的语法是错误的:
<a href="http://www.example.com'>示例站点链接</a>
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>我的测试站点</title> </head> <body> <p>这是我的页面</p> </body> </html>
1,<!DOCTYPE html>: 声明文档类型. 很久以前,早期的HTML(大约1991年2月),文档类型声明类似于链接,规定了HTML页面必须遵从的良好规则,能自动检测错误和其他有用的东西。使用如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
然而现在没有人再这样写,需要保证每一个东西都正常工作已成为历史。你只需要知道<!DOCTYPE html>是最短的有效的文档声明。
2,<html></html>: <html>元素。这个元素包裹了整个完整的页面,是一个根元素。
3,<head></head>: <head>元素. 这个元素是一个容器,它包含了所有你想包含在HTML页面中但不想在HTML页面中显示的内容。这些内容包括你想在搜索结果中出现的关键字和页面描述,CSS样式,字符集声明等等。以后的章节能学到更多关于<head>元素的内容。
4.<meta charset="utf-8">: 这个元素设置文档使用utf-8字符集编码,utf-8字符集包含了人类大部分的文字。基本上他能识别你放上去的所有文本内容。毫无疑问要使用它,并且它能在以后避免很多其他问题。
5.<title></title>: 设置页面标题,出现在浏览器标签上,当你标记/收藏页面时它可用来描述页面。
6.<body></body>: <body>元素。 包含了你访问页面时所有显示在页面上的内容,文本,图片,音频,游戏等等。
代码中包含的空格是没有必要的;下面的两个代码片段是等价的:
<p>狗 狗 很 呆 萌。</p> <p>狗 狗 很 呆 萌。</p>
渲染这些代码的时候,HTML解释器会将连续出现的空白字符减少为一个单独的空格符。
那么为什么我们会使用那么多的空白呢? 可读性 —— 如果你的代码被很好地进行格式化,那么就很容易理解你的代码。
为了将一段HTML中的内容置为注释,你需要将其用特殊的记号<!--和-->包括起来, 比如:
<p>我在注释外!</p> <!-- <p>我在注释内!</p> -->
HTML 头部是包含在<head> 元素里面的内容。不像 <body>元素的内容会显示在浏览器中,head 里面的内容不会在浏览器中显示,它的作用是包含一些页面的元数据。
元数据就是描述数据的数据,而HTML有一个“官方的”方式来为一个文档添加元数据—— <meta> 。有很多不同种类的 <meta> 可以被包含进你的页面的<head>元素,比如。
指定字符的编码
<meta charset="utf-8">
这个元素简单的指定了文档的字符编码 —— 在这个文档中被允许使用的字符集。 utf-8 是一个通用的字符集,它包含了任何人类语言中的大部分的字符。
添加作者和描述
CSS和JavaScript
如今,几乎你使用的所有网站都会使用css让网页更加炫酷,使用js让网页有交互功能,比如视频播放器,地图,游戏以及更多功能。这些应用在网页中很常见,它们分别使用<link>元素以及 <script> 元素。
<link>元素经常位于文档的头部。这个link元素有2个属性,rel="stylesheet"表明这是文档的样式表,而 href包含了样式表文件的路径:
<link rel="stylesheet" href="my-css-file.css">
<script>部分没必要非要放在文档头部;实际上,把它放在文档的尾部(在 </body>标签之前)是一个更好的选择,这样可以确保在加载脚本之前浏览器已经解析了HTML内容(如果脚本加载某个不存在的元素,浏览器会报错)。
<script src="my-js-file.js"></script>
在HTML中,每个段落是通过<p>元素标签进行定义的, 比如下面这样:
<p>我是一个段落,千真万确。</p>
每个标题(Heading)是通过“标题标签”进行定义的:
<h1>我是文章的标题</h1>
这里有六个标题元素标签 —— <h1>、<h2>、<h3>、<h4>、<h5>、<h6>。每个元素代表文档中不同级别的内容; <h1> 表示主标题(the main heading),<h2> 表示二级子标题(subheadings),<h3> 表示三级子标题(sub-subheadings),等等。
<ol> <li>先用蛋白一个、盐半茶匙及淀粉两大匙搅拌均匀,调成“腌料”,鸡胸肉切成约一厘米见方的碎丁并用“腌料”搅拌均匀,腌渍半小时。</li> <li>用酱油一大匙、淀粉水一大匙、糖半茶匙、盐四分之一茶匙、白醋一茶匙、蒜末半茶匙调拌均匀,调成“综合调味料”。</li> <li>鸡丁腌好以后,色拉油下锅烧热,先将鸡丁倒入锅内,用大火快炸半分钟,炸到变色之后,捞出来沥干油汁备用。</li> <li>在锅里留下约两大匙油,烧热后将切好的干辣椒下锅,用小火炒香后,再放入花椒粒和葱段一起爆香。随后鸡丁重新下锅,用大火快炒片刻后,再倒入“综合调味料”继续快炒。 <ul> <li>如果你采用正宗川菜做法,最后只需加入花生米,炒拌几下就可以起锅了。</li> <li>如果你在北方,可加入黄瓜丁、胡萝卜丁和花生米,翻炒后起锅。</li> </ul> </li> </ol>
<i> 被用来传达传统上用斜体表达的意义:外国文字,分类名称,技术术语,一种思想……
<b> 被用来传达传统上用粗体表达的意义:关键字,产品名称,引导句……
<u> 被用来传达传统上用下划线表达的意义:专有名词,拼写错误……
<strong>强调重要的词
通过将文本转换为<a>元素内的链接来创建基本链接, 给它一个href属性(也称为目标),它将包含您希望链接指向的网址。
<p>I'm creating a link to <a href="https://www.***.com/">***</a>. </p>
使用title属性添加支持信息
<p>I'm creating a link to <a href="https://www.baidu.com" title="这是百度">百度</a>. </p>
I'm creating a link to 百度.
块级链接
可以将一些内容转换为链接,甚至是块级元素。例如你想要将一个图像转换为链接,你只需把图像元素放到<a></a>标签中间。
文档片段
超链接除了可以链接到文档外,也可以链接到HTML文档的特定部分(被称为文档片段)。要做到这一点,你必须首先给要链接到的元素分配一个id属性。例如,如果你想链接到一个特定的标题,可以这样做:
<h2 id="Mailing_address">Mailing address</h2>
然后链接到那个特定的id,可以在URL的结尾使用一个哈希符号(#)指向它,例如:
<p>Want to write us a letter? Use our <a href="contacts.html#Mailing_address">mailing address</a>.</p>
你甚至可以在同一份文档下,通过链接文档片段,来链接到同一份文档的另一部分:
<p>The <a href="#Mailing_address">company mailing address</a> can be found at the bottom of this page.</p>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>增加学生</title> </head> <body> <form method="post" action="/community/alpha/student"> <p> 姓名:<input type="text" name="name"> </p> <p> 年龄:<input type="text" name="age"> </p> <p> <input type="submit" value="保存"> </p> </form> </body> </html>
//post @RequestMapping(path = "/student",method = RequestMethod.POST) @ResponseBody public String saveStudent(String name,int age){ System.out.println(name); System.out.println(age); return "success"; }
大部分用来定义表单小部件的元素都有一些他们自己的属性。然而,在所有表单元素中都有一组通用属性,它们可以对这些小部件进行控制。下面是这些通用属性的列表:
密码框
通过设置type属性值为password来设置该类型框:
<input type="password" id="pwd" name="pwd">
搜索框
通过设置 type属性值为 search 来设置该类型框:
<input type="search" id="search" name="search">
电话号码栏:
通过 type属性的 tel 值设置该类型框:
<input type="tel" id="tel" name="tel">
URL 栏
通过type属性的url 值设置该类型框:
<input type="url" id="url" name="url">
多行文本框
多行文本框专指使用<textarea>元素,而不是使用<input>元素。
<textarea cols="30" rows="10"></textarea>
在HTML表单中,有三种按钮:
Submit
将表单数据发送到服务器。对于<button>元素, 省略 type 属性 (或是一个无效的 type 值) 的结果就是一个提交按钮.
Reset
将所有表单小部件重新设置为它们的默认值。
Anonymous
没有自动生效的按钮,但是可以使用JavaScript代码进行定制。
每次向服务器发送数据时,都需要考虑安全性。到目前为止,HTML表单是最常见的攻击路径(可能发生攻击的地方)。这些问题从来都不是来自HTML表单本身,它们来自于服务器如何处理数据。
根据你所做的事情,你会遇到一些非常有名的安全问题:
跨站脚本(XSS)和跨站点请求伪造(CSRF)是常见的攻击类型,它们发生在当您将用户发送的数据显示给这个用户或另一个用户时。
XSS允许攻击者将客户端脚本注入到其他用户查看的Web页面中。攻击者可以使用跨站点脚本攻击的漏洞来绕过诸如同源策略之类的访问控制。这些攻击的影响可能从一个小麻烦到一个重大的安全风险。
CSRF攻击类似于XSS攻击,因为它们以相同的方式开始攻击——向Web页面中注入客户端脚本——但它们的目标是不同的。CSRF攻击者试图将权限升级到特权用户(比如站点管理员)的级别,以执行他们不应该执行的操作(例如,将数据发送给一个不受信任的用户)。
XSS攻击利用用户对web站点的信任,而CSRF攻击则利用网站对其用户的信任。
为了防止这些攻击,您应该始终检查用户发送给服务器的数据(如果需要显示),尽量不要显示用户提供的HTML内容。相反,您应该对用户提供的数据进行处理,这样您就不会逐字地显示它。当今市场上几乎所有的框架都实现了一个最小的过滤器,它可以从任何用户发送的数据中删除HTML<script>、<iframe> 和<object>元素。这有助于降低风险,但并不一定会消除风险。
SQL 注入是一种试图在目标web站点使用的数据库上执行操作的攻击类型。这通常包括发送一个SQL请求,希望服务器能够执行它(通常发生在应用服务器试图存储由用户发送的数据时)。这实际上是攻击网站的主要途径之一。
其后果可能是可怕的,从数据丢失到通过使用特权升级控制整个网站基础设施的攻击。这是一个非常严重的威胁,您永远不应该存储用户发送的数据,而不执行一些清理工作(例如,在php/mysql基础设施上使用mysql_real_escape_string()
这种类型的攻击出现在当您的应用程序基于表单上用户的数据输入构建HTTP头部或电子邮件时。这些不会直接损害您的服务器或影响您的用户,但它们会引发一个更深入的问题,例如会话劫持或网络钓鱼攻击。
这些攻击大多是无声的,并且可以将您的服务器变成僵尸。
偏执:永远不要相信你的用户
那么,你如何应对这些威胁呢?这是一个远远超出本指南的主题,不过有一些规则需要牢记。最重要的原则是:永远不要相信你的用户,包括你自己;即使是一个值得信赖的用户也可能被劫持。
所有到达服务器的数据都必须经过检查和消毒。总是这样。没有例外。
远离有潜在危险的字符转义。应该如何谨慎使用的特定字符取决于所使用的数据的上下文和所使用的服务器平台,但是所有的服务器端语言都有相应的功能。
限制输入的数据量,只允许有必要的数据。
沙箱上传文件(将它们存储在不同的服务器上,只允许通过不同的子域访问文件,或者通过完全不同的域名访问文件更好)。
最后,我自己是一名从事了多年开发的JAVA老程序员,辞职目前在做自己的java私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的java学习干货,可以送给每一位喜欢java的小伙伴,想要获取的可以关注我的头条号并在后台私信我:交流,即可免费获取。
*请认真填写需求信息,我们会在24小时内与您取得联系。