-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.html
1377 lines (926 loc) · 123 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 7.3.0">
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png">
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png">
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png">
<link rel="mask-icon" href="/images/logo.svg" color="#222">
<link rel="stylesheet" href="/css/main.css">
<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">
<script id="hexo-configurations">
var NexT = window.NexT || {};
var CONFIG = {"hostname":"example.com","root":"/","scheme":"Gemini","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":null,"storage":true,"lazyload":false,"nav":null},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},"path":"search.xml"};
</script>
<meta property="og:type" content="website">
<meta property="og:title" content="WenQing">
<meta property="og:url" content="http://example.com/index.html">
<meta property="og:site_name" content="WenQing">
<meta property="og:locale" content="zh_CN">
<meta property="article:author" content="WenQing">
<meta name="twitter:card" content="summary">
<link rel="canonical" href="http://example.com/">
<script id="page-configurations">
// https://hexo.io/docs/variables.html
CONFIG.page = {
sidebar: "",
isHome : true,
isPost : false,
lang : 'zh-CN'
};
</script>
<title>WenQing</title>
<noscript>
<style>
.use-motion .brand,
.use-motion .menu-item,
.sidebar-inner,
.use-motion .post-block,
.use-motion .pagination,
.use-motion .comments,
.use-motion .post-header,
.use-motion .post-body,
.use-motion .collection-header { opacity: initial; }
.use-motion .site-title,
.use-motion .site-subtitle {
opacity: initial;
top: initial;
}
.use-motion .logo-line-before i { left: initial; }
.use-motion .logo-line-after i { right: initial; }
</style>
</noscript>
</head>
<body itemscope itemtype="http://schema.org/WebPage">
<div class="container use-motion">
<div class="headband"></div>
<header class="header" itemscope itemtype="http://schema.org/WPHeader">
<div class="header-inner"><div class="site-brand-container">
<div class="site-nav-toggle">
<div class="toggle" aria-label="切换导航栏">
<span class="toggle-line toggle-line-first"></span>
<span class="toggle-line toggle-line-middle"></span>
<span class="toggle-line toggle-line-last"></span>
</div>
</div>
<div class="site-meta">
<a href="/" class="brand" rel="start">
<span class="logo-line-before"><i></i></span>
<h1 class="site-title">WenQing</h1>
<span class="logo-line-after"><i></i></span>
</a>
</div>
<div class="site-nav-right">
<div class="toggle popup-trigger">
</div>
</div>
</div>
<nav class="site-nav">
<ul id="menu" class="main-menu menu">
<li class="menu-item menu-item-home">
<a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>
</li>
<li class="menu-item menu-item-about">
<a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>
</li>
<li class="menu-item menu-item-tags">
<a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>
</li>
<li class="menu-item menu-item-categories">
<a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>
</li>
<li class="menu-item menu-item-archives">
<a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>
</li>
<li class="menu-item menu-item-schedule">
<a href="/schedule/" rel="section"><i class="fa fa-calendar fa-fw"></i>日程表</a>
</li>
</ul>
</nav>
</div>
</header>
<div class="back-to-top">
<i class="fa fa-arrow-up"></i>
<span>0%</span>
</div>
<main class="main">
<div class="main-inner">
<div class="content-wrap">
<div class="content index posts-expand">
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2024/11/20/2024/go%E9%9D%A2%E8%AF%95%E8%AE%B0%E5%BD%95/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2024/11/20/2024/go%E9%9D%A2%E8%AF%95%E8%AE%B0%E5%BD%95/" class="post-title-link" itemprop="url">go易错实例1</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2024-11-20 08:53:52" itemprop="dateCreated datePublished" datetime="2024-11-20T08:53:52+08:00">2024-11-20</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>解压密码:30a9cd357a436d80cd05db0dd5ba4067</p>
<h4 id="数组长度和容量固定且相同"><a href="#数组长度和容量固定且相同" class="headerlink" title="数组长度和容量固定且相同"></a>数组长度和容量固定且相同</h4><h5 id="类型值"><a href="#类型值" class="headerlink" title="类型值"></a>类型值</h5><p>一个类型的实例称为次类型的一个值</p>
<p>一个类型可以有很多不同的值,其中一个为它的零值</p>
<p>每个类型都会有自己的零值,零值可以看做是这个类型的默认值</p>
<h5 id="指针"><a href="#指针" class="headerlink" title="指针"></a>指针</h5><p>当一个变量被声明的时候,go运行时将为此变量开辟一段内存,此内存的起始地址即为此变量的地址</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2024/11/20/2024/go%E6%98%93%E9%94%99%E5%AE%9E%E4%BE%8B/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2024/11/20/2024/go%E6%98%93%E9%94%99%E5%AE%9E%E4%BE%8B/" class="post-title-link" itemprop="url">go易错实例1</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2024-11-20 08:53:52" itemprop="dateCreated datePublished" datetime="2024-11-20T08:53:52+08:00">2024-11-20</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<h3 id="go易错分析1"><a href="#go易错分析1" class="headerlink" title="go易错分析1"></a>go易错分析1</h3><h5 id="1-强大的go接口interface"><a href="#1-强大的go接口interface" class="headerlink" title="1. 强大的go接口interface"></a>1. 强大的go接口interface</h5><p>下面是一个go方法,其中的参数都是通过interface进行定义的</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">func CpSrcToDst(src io.Reader, dst io.Writer) (err error) {</span><br><span class="line"> return nil</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="常见的行为"><a href="#常见的行为" class="headerlink" title="常见的行为"></a>常见的行为</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">func Interface interface{</span><br><span class="line"> Len() int</span><br><span class="line"> Less(i,j int) bool</span><br><span class="line"> Swap(i,j int)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="解耦"><a href="#解耦" class="headerlink" title="解耦"></a>解耦</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">// PaymentInterface 定义支付接口,所有具体的支付方式都需要实现这个接口</span><br><span class="line">type PaymentInterface interface {</span><br><span class="line"> Pay(amount float64) error</span><br><span class="line">}</span><br><span class="line">// ProcessPayment 处理支付的函数,通过接口调用具体的支付逻辑,不依赖具体支付方式的实现细节</span><br><span class="line">func ProcessPayment(payer PaymentInterface, amount float64) error {</span><br><span class="line"> return payer.Pay(amount)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h6 id="限制行为"><a href="#限制行为" class="headerlink" title="限制行为"></a>限制行为</h6><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">// 传递只读的接口</span><br><span class="line">type CfgGetter interface{</span><br><span class="line"> Get() int</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>大多数情况下,接口应该作用于消费者端</p>
<p>返回一个接口,会限制灵活性;</p>
<p>做什么要保守,接收什么要自由;返回结构体而不是接口(大多数),尽可能的接收接口;</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2024/11/19/2024/Untitled/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2024/11/19/2024/Untitled/" class="post-title-link" itemprop="url">未命名</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2024-11-19 13:07:40" itemprop="dateCreated datePublished" datetime="2024-11-19T13:07:40+08:00">2024-11-19</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2024/11/19/hello-world/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2024/11/19/hello-world/" class="post-title-link" itemprop="url">Hello World</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2024-11-19 13:06:33" itemprop="dateCreated datePublished" datetime="2024-11-19T13:06:33+08:00">2024-11-19</time>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>Welcome to <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>! This is your very first post. Check <a target="_blank" rel="noopener" href="https://hexo.io/docs/">documentation</a> for more info. If you get any problems when using Hexo, you can find the answer in <a target="_blank" rel="noopener" href="https://hexo.io/docs/troubleshooting.html">troubleshooting</a> or you can ask me on <a target="_blank" rel="noopener" href="https://github.com/hexojs/hexo/issues">GitHub</a>.</p>
<h2 id="Quick-Start"><a href="#Quick-Start" class="headerlink" title="Quick Start"></a>Quick Start</h2><h3 id="Create-a-new-post"><a href="#Create-a-new-post" class="headerlink" title="Create a new post"></a>Create a new post</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="string">"My New Post"</span></span><br></pre></td></tr></table></figure>
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/writing.html">Writing</a></p>
<h3 id="Run-server"><a href="#Run-server" class="headerlink" title="Run server"></a>Run server</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure>
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/server.html">Server</a></p>
<h3 id="Generate-static-files"><a href="#Generate-static-files" class="headerlink" title="Generate static files"></a>Generate static files</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure>
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/generating.html">Generating</a></p>
<h3 id="Deploy-to-remote-sites"><a href="#Deploy-to-remote-sites" class="headerlink" title="Deploy to remote sites"></a>Deploy to remote sites</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure>
<p>More info: <a target="_blank" rel="noopener" href="https://hexo.io/docs/one-command-deployment.html">Deployment</a></p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/" class="post-title-link" itemprop="url">彻底搞通https加密原理</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2022-06-01 00:00:00" itemprop="dateCreated datePublished" datetime="2022-06-01T00:00:00+08:00">2022-06-01</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2024-11-23 16:34:34" itemprop="dateModified" datetime="2024-11-23T16:34:34+08:00">2024-11-23</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/%E7%BD%91%E7%BB%9C/" itemprop="url" rel="index"><span itemprop="name">网络</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>HTTPS(SSL/TLS)的加密机制虽然是大家都应了解的基本知识,但网上很多相关文章总会忽略一些内容,没有阐明完整的逻辑脉络,我学习它的时候也曾废了些功夫。</p>
<p>对称与非对称加密、数字签名、数字证书等,在学习过程中,除了了解“它是什么”,你是否有想过“为什么是它”?我认为有必要搞清楚后者,否则你可能只是单纯地记住了被灌输的知识,而未真正理解它。</p>
<p>本文以问题的形式逐步展开,一步步解开HTTPS的面纱,希望能帮助你彻底搞懂HTTPS!</p>
<p>(阅读完需要花些时间,欢迎收藏、点赞~)</p>
<h2 id="为什么需要加密?"><a href="#为什么需要加密?" class="headerlink" title="为什么需要加密?"></a><strong>为什么需要加密?</strong></h2><p>因为http的内容是明文传输的,明文数据会经过中间代理服务器、路由器、wifi热点、通信服务运营商等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是<code>中间人攻击</code>。所以我们才需要对信息进行加密。最容易理解的就是<code>对称加密</code> 。</p>
<h2 id="什么是对称加密?"><a href="#什么是对称加密?" class="headerlink" title="什么是对称加密?"></a><strong>什么是对称加密?</strong></h2><p>简单说就是有一个密钥,它可以加密一段信息,也可以对加密后的信息进行解密,和我们日常生活中用的钥匙作用差不多。</p>
<img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-1252526c263ee50c47b7b49bd1c2ef44_1440w.jpg" class="" title="img">
<p>对称加密(<a target="_blank" rel="noopener" href="https://sectigostore.com/blog/types-of-encryption-what-to-know-about-symmetric-vs-asymmetric-encryption/%EF%BC%89">https://sectigostore.com/blog/types-of-encryption-what-to-know-about-symmetric-vs-asymmetric-encryption/)</a></p>
<h2 id="用对称加密可行吗?"><a href="#用对称加密可行吗?" class="headerlink" title="用对称加密可行吗?"></a><strong>用对称加密可行吗?</strong></h2><p><strong>如果通信双方都各自持有同一个密钥,且没有别人知道,这两方的通信安全当然是可以被保证的(除非密钥被破解)。</strong></p>
<p>然而最大的问题就是<strong>这个密钥怎么让传输的双方知晓,同时不被别人知道</strong>。如果由服务器生成一个密钥并传输给浏览器,那在这个传输过程中密钥被别人劫持到手了怎么办?之后他就能用密钥解开双方传输的任何内容了,所以这么做当然不行。</p>
<p>换种思路?试想一下,如果浏览器内部就预存了网站A的密钥,且可以确保除了浏览器和网站A,不会有任何外人知道该密钥,那理论上用对称加密是可以的,这样浏览器只要预存好世界上所有HTTPS网站的密钥就行了!这么做显然不现实。<br>怎么办?所以我们就需要<code>非对称加密</code> 。</p>
<h2 id="什么是非对称加密?"><a href="#什么是非对称加密?" class="headerlink" title="什么是非对称加密?"></a><strong>什么是非对称加密?</strong></h2><p>简单说就是有两把密钥,通常一把叫做公钥、一把叫私钥,用公钥加密的内容必须用私钥才能解开,同样,私钥加密的内容只有公钥能解开。</p>
<img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-1589bbfab027eb9f52da03c02a837fd4_1440w.jpg" class="" title="img">
<p>非对称加密(<a target="_blank" rel="noopener" href="https://sectigostore.com/blog/types-of-encryption-what-to-know-about-symmetric-vs-asymmetric-encryption/%EF%BC%89">https://sectigostore.com/blog/types-of-encryption-what-to-know-about-symmetric-vs-asymmetric-encryption/)</a></p>
<h2 id="用非对称加密可行吗?"><a href="#用非对称加密可行吗?" class="headerlink" title="用非对称加密可行吗?"></a>用非对称加密可行吗?</h2><p>鉴于非对称加密的机制,我们可能会有这种思路:服务器先把公钥以明文方式传输给浏览器,之后浏览器向服务器传数据前都先用这个公钥加密好再传,这条数据的安全似乎可以保障了!<strong>因为只有服务器有相应的私钥能解开公钥加密的数据</strong>。</p>
<p>然而反过来<strong>由服务器到浏览器的这条路怎么保障安全?</strong>如果服务器用它的私钥加密数据传给浏览器,那么浏览器用公钥可以解密它,而这个公钥是一开始通过明文传输给浏览器的,若这个公钥被中间人劫持到了,那他也能用该公钥解密服务器传来的信息了。所以<strong>目前似乎只能保证由浏览器向服务器传输数据的安全性</strong>(其实仍有漏洞,下文会说),那利用这点你能想到什么解决方案吗?</p>
<h2 id="改良的非对称加密方案,似乎可以?"><a href="#改良的非对称加密方案,似乎可以?" class="headerlink" title="改良的非对称加密方案,似乎可以?"></a><strong>改良的非对称加密方案,似乎可以?</strong></h2><p>我们已经理解通过一组公钥私钥,可以保证单个方向传输的安全性,那用两组公钥私钥,是否就能保证双向传输都安全了?请看下面的过程:</p>
<ol>
<li>某网站服务器拥有公钥A与对应的私钥A’;浏览器拥有公钥B与对应的私钥B’。</li>
<li>浏览器把公钥B明文传输给服务器。</li>
<li>服务器把公钥A明文给传输浏览器。</li>
<li>之后浏览器向服务器传输的内容都用公钥A加密,服务器收到后用私钥A’解密。由于只有服务器拥有私钥A’,所以能保证这条数据的安全。</li>
<li>同理,服务器向浏览器传输的内容都用公钥B加密,浏览器收到后用私钥B’解密。同上也可以保证这条数据的安全。</li>
</ol>
<p>的确可以!抛开这里面仍有的漏洞不谈(下文会讲),HTTPS的加密却没使用这种方案,为什么?很重要的原因是非对称加密算法非常耗时,而对称加密快很多。那我们能不能运用非对称加密的特性解决前面提到的对称加密的漏洞?</p>
<h2 id="非对称加密-对称加密?"><a href="#非对称加密-对称加密?" class="headerlink" title="非对称加密+对称加密?"></a><strong>非对称加密+对称加密?</strong></h2><p>既然非对称加密耗时,那非对称加密+对称加密结合可以吗?而且得尽量减少非对称加密的次数。当然是可以的,且非对称加密、解密各只需用一次即可。<br>请看一下这个过程:</p>
<ol>
<li>某网站拥有用于非对称加密的公钥A、私钥A’。</li>
<li>浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器。</li>
<li>浏览器随机生成一个用于对称加密的密钥X,用公钥A加密后传给服务器。</li>
<li>服务器拿到后用私钥A’解密得到密钥X。</li>
<li>这样双方就都拥有密钥X了,且别人无法知道它。之后双方所有数据都通过密钥X加密解密即可。</li>
</ol>
<p>完美!HTTPS基本就是采用了这种方案。完美?还是有漏洞的。</p>
<h2 id="中间人攻击"><a href="#中间人攻击" class="headerlink" title="中间人攻击"></a><strong>中间人攻击</strong></h2><img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-daeb6b8b0ac5411d51d60bd08b551789_1440w.jpg" class="" title="img">
<p>中间人攻击(<a target="_blank" rel="noopener" href="https://blog.pradeo.com/man-in-the-middle-attack%EF%BC%89">https://blog.pradeo.com/man-in-the-middle-attack)</a></p>
<p>如果在数据传输过程中,中间人劫持到了数据,此时他的确无法得到浏览器生成的密钥X,这个密钥本身被公钥A加密了,只有服务器才有私钥A’解开它,然而中间人却完全不需要拿到私钥A’就能干坏事了。请看:</p>
<ol>
<li>某网站有用于非对称加密的公钥A、私钥A’。</li>
<li>浏览器向网站服务器请求,服务器把公钥A明文给传输浏览器。</li>
<li><strong>中间人劫持到公钥A,保存下来,把数据包中的公钥A替换成自己伪造的公钥B(它当然也拥有公钥B对应的私钥B’)</strong>。</li>
<li>浏览器生成一个用于对称加密的密钥X,用<strong>公钥B</strong>(浏览器无法得知公钥被替换了)加密后传给服务器。</li>
<li><strong>中间人劫持后用私钥B’解密得到密钥X,再用公钥A加密后传给服务器</strong>。</li>
<li>服务器拿到后用私钥A’解密得到密钥X。</li>
</ol>
<p>这样在双方都不会发现异常的情况下,中间人通过一套“狸猫换太子”的操作,掉包了服务器传来的公钥,进而得到了密钥X。<strong>根本原因是浏览器无法确认收到的公钥是不是网站自己的,</strong>因为公钥本身是明文传输的,难道还得对公钥的传输进行加密?这似乎变成鸡生蛋、蛋生鸡的问题了。解法是什么?</p>
<h2 id="如何证明浏览器收到的公钥一定是该网站的公钥?"><a href="#如何证明浏览器收到的公钥一定是该网站的公钥?" class="headerlink" title="如何证明浏览器收到的公钥一定是该网站的公钥?"></a><strong>如何证明浏览器收到的公钥一定是该网站的公钥?</strong></h2><p>其实所有证明的源头都是一条或多条不证自明的“公理”(可以回想一下数学上公理),由它推导出一切。比如现实生活中,若想证明某身份证号一定是小明的,可以看他身份证,而身份证是由政府作证的,这里的“公理”就是“政府机构可信”,这也是社会正常运作的前提。</p>
<p>那能不能类似地有个机构充当互联网世界的“公理”呢?让它作为一切证明的源头,给网站颁发一个“身份证”?</p>
<p>它就是<strong>CA机构</strong>,它是如今互联网世界正常运作的前提,而CA机构颁发的“身份证”就是<strong>数字证书</strong>。</p>
<h2 id="数字证书"><a href="#数字证书" class="headerlink" title="数字证书"></a><strong>数字证书</strong></h2><img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-1123c51b7d4a0db707100971b91728a9_1440w.jpg" class="" title="img">
<p>网站在使用HTTPS前,需要向<strong>CA机构</strong>申领一份<strong>数字证书</strong>,数字证书里含有证书持有者信息、公钥信息等。服务器把证书传输给浏览器,浏览器从证书里获取公钥就行了,证书就如身份证,证明“该公钥对应该网站”。而这里又有一个显而易见的问题,“<strong>证书本身的传输过程中,如何防止被篡改”</strong>?即如何证明证书本身的真实性?身份证运用了一些防伪技术,而数字证书怎么防伪呢?解决这个问题我们就接近胜利了!</p>
<h2 id="如何放防止数字证书被篡改?"><a href="#如何放防止数字证书被篡改?" class="headerlink" title="如何放防止数字证书被篡改?"></a><strong>如何放防止数字证书被篡改?</strong></h2><p>我们把证书原本的内容生成一份“签名”,比对证书内容和签名是否一致就能判别是否被篡改。这就是数字证书的“防伪技术”,这里的“签名”就叫<code>数字签名</code>:</p>
<h2 id="数字签名"><a href="#数字签名" class="headerlink" title="数字签名"></a><strong>数字签名</strong></h2><p>这部分内容建议看下图并结合后面的文字理解,图中左侧是数字签名的制作过程,右侧是验证过程:</p>
<img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-7c78935389af46e197e96d9cd91c06dd_1440w.jpg" class="" title="img">
<p>数字签名的生成与验证(<a target="_blank" rel="noopener" href="https://cheapsslsecurity.com/blog/digital-signature-vs-digital-certificate-the-difference-explained/%EF%BC%89">https://cheapsslsecurity.com/blog/digital-signature-vs-digital-certificate-the-difference-explained/)</a></p>
<p>数字签名的制作过程:</p>
<ol>
<li>CA机构拥有非对称加密的私钥和公钥。</li>
<li>CA机构对证书明文数据T进行hash。</li>
<li>对hash后的值用私钥加密,得到数字签名S。</li>
</ol>
<p>明文和数字签名共同组成了数字证书,这样一份数字证书就可以颁发给网站了。<br>那浏览器拿到服务器传来的数字证书后,如何验证它是不是真的?(有没有被篡改、掉包)</p>
<p>浏览器验证过程:</p>
<ol>
<li>拿到证书,得到明文T,签名S。</li>
<li>用CA机构的公钥对S解密(由于是浏览器信任的机构,所以浏览器保有它的公钥。详情见下文),得到S’。</li>
<li>用证书里指明的hash算法对明文T进行hash得到T’。</li>
<li>显然通过以上步骤,T’应当等于S‘,除非明文或签名被篡改。所以此时比较S’是否等于T’,等于则表明证书可信。</li>
</ol>
<p>为何么这样可以保证证书可信呢?我们来仔细想一下。</p>
<h2 id="中间人有可能篡改该证书吗?"><a href="#中间人有可能篡改该证书吗?" class="headerlink" title="中间人有可能篡改该证书吗?"></a><strong>中间人有可能篡改该证书吗?</strong></h2><p>假设中间人篡改了证书的原文,由于他没有CA机构的私钥,所以无法得到此时加密后签名,无法相应地篡改签名。浏览器收到该证书后会发现原文和签名解密后的值不一致,则说明证书已被篡改,证书不可信,从而终止向服务器传输信息,防止信息泄露给中间人。</p>
<p>既然不可能篡改,那整个证书被掉包呢?</p>
<h2 id="中间人有可能把证书掉包吗?"><a href="#中间人有可能把证书掉包吗?" class="headerlink" title="中间人有可能把证书掉包吗?"></a><strong>中间人有可能把证书掉包吗?</strong></h2><p>假设有另一个网站B也拿到了CA机构认证的证书,它想劫持网站A的信息。于是它成为中间人拦截到了A传给浏览器的证书,然后替换成自己的证书,传给浏览器,之后浏览器就会错误地拿到B的证书里的公钥了,这确实会导致上文“中间人攻击”那里提到的漏洞?</p>
<p>其实这并不会发生,因为证书里包含了网站A的信息,包括域名,浏览器把证书里的域名与自己请求的域名比对一下就知道有没有被掉包了。</p>
<h2 id="为什么制作数字签名时需要hash一次?"><a href="#为什么制作数字签名时需要hash一次?" class="headerlink" title="为什么制作数字签名时需要hash一次?"></a><strong>为什么制作数字签名时需要hash一次?</strong></h2><p>我初识HTTPS的时候就有这个疑问,因为似乎那里的hash有点多余,把hash过程去掉也能保证证书没有被篡改。</p>
<p>最显然的是性能问题,前面我们已经说了非对称加密效率较差,证书信息一般较长,比较耗时。而hash后得到的是固定长度的信息(比如用md5算法hash后可以得到固定的128位的值),这样加解密就快很多。</p>
<p>当然也有安全上的原因,这部分内容相对深一些,感兴趣的可以看这篇解答:<a href="https://link.zhihu.com/?target=https://link.juejin.im/?target=https://crypto.stackexchange.com/a/12780">crypto.stackexchange.com/a/12780</a></p>
<h2 id="怎么证明CA机构的公钥是可信的?"><a href="#怎么证明CA机构的公钥是可信的?" class="headerlink" title="怎么证明CA机构的公钥是可信的?"></a><strong>怎么证明CA机构的公钥是可信的?</strong></h2><p>你们可能会发现上文中说到CA机构的公钥,我几乎一笔带过,“浏览器保有它的公钥”,这是个什么保有法?怎么证明这个公钥是否可信?</p>
<p>让我们回想一下数字证书到底是干啥的?没错,为了证明某公钥是可信的,即“该公钥是否对应该网站”,那CA机构的公钥是否也可以用数字证书来证明?没错,操作系统、浏览器本身会预装一些它们信任的根证书,如果其中会有CA机构的根证书,这样就可以拿到它对应的可信公钥了。</p>
<p>实际上证书之间的认证也可以不止一层,可以A信任B,B信任C,以此类推,我们把它叫做<code>信任链</code>或<code>数字证书链</code>。也就是一连串的数字证书,由根证书为起点,透过层层信任,使终端实体证书的持有者可以获得转授的信任,以证明身份。</p>
<p>另外,不知你们是否遇到过网站访问不了、提示需安装证书的情况?这里安装的就是根证书。说明浏览器不认给这个网站颁发证书的机构,那么你就得手动下载安装该机构的根证书(风险自己承担XD)。安装后,你就有了它的公钥,就可以用它验证服务器发来的证书是否可信了。</p>
<img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-04cd27f3f46388df2d8d70375c4ecac5_1440w.jpg" class="" title="img">
<p>信任链(<a target="_blank" rel="noopener" href="https://publib.boulder.ibm.com/tividd/td/TRM/GC32-1323-00/en_US/HTML/admin230.htm%EF%BC%89">https://publib.boulder.ibm.com/tividd/td/TRM/GC32-1323-00/en_US/HTML/admin230.htm)</a></p>
<h2 id="每次进行HTTPS请求时都必须在SSL-TLS层进行握手传输密钥吗?"><a href="#每次进行HTTPS请求时都必须在SSL-TLS层进行握手传输密钥吗?" class="headerlink" title="每次进行HTTPS请求时都必须在SSL/TLS层进行握手传输密钥吗?"></a><strong>每次进行HTTPS请求时都</strong>必须<strong>在SSL/TLS层进行握手传输密钥吗?</strong></h2><p>这也是我当时的困惑之一,显然每次请求都经历一次密钥传输过程非常耗时,那怎么达到只传输一次呢?</p>
<p>服务器会为每个浏览器(或客户端软件)维护一个session ID,在TLS握手阶段传给浏览器,浏览器生成好密钥传给服务器后,服务器会把该密钥存到相应的session ID下,之后浏览器每次请求都会携带session ID,服务器会根据session ID找到相应的密钥并进行解密加密操作,这样就不必要每次重新制作、传输密钥了!</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a><strong>总结</strong></h2><p>可以看下这张图,梳理一下整个流程(SSL、TLS握手有一些区别,不同版本间也有区别,不过大致过程就是这样):</p>
<img src="/2022/06/01/2022/%E5%BD%BB%E5%BA%95%E6%90%9E%E9%80%9Ahttps/v2-a0d10af45e785fe8d3f5cd12f8c309f5_1440w.jpg" class="" title="img">
<p>(<a target="_blank" rel="noopener" href="http://www.extremetech.com)/">www.extremetech.com)</a></p>
<p>至此,我们已自上而下地打通了HTTPS加密的整体脉络以及核心知识点,不知你是否真正搞懂了HTTPS呢?<br>找几个时间,多看、多想、多理解几次就会越来越清晰的!<br>那么,下面的问题你是否已经可以解答了呢?</p>
<ol>
<li>为什么要用对称加密+非对称加密?</li>
<li>为什么不能只用非对称加密?</li>
<li>为什么需要数字证书?</li>
<li>为什么需要数字签名?<br>…</li>
</ol>
<p>当然,由于篇幅和能力所限,一些更深入的内容没有覆盖到。但我认为一般对于前后端开发人员来说,了解到这步就够了,有兴趣的可以再深入研究~如有疏漏之处,欢迎指出。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2019/03/01/2019/mk_http/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2019/03/01/2019/mk_http/" class="post-title-link" itemprop="url">mk_http</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2019-03-01 00:00:00" itemprop="dateCreated datePublished" datetime="2019-03-01T00:00:00+08:00">2019-03-01</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2024-11-21 09:12:15" itemprop="dateModified" datetime="2024-11-21T09:12:15+08:00">2024-11-21</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/http/" itemprop="url" rel="index"><span itemprop="name">http</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2018/12/10/2018/go%E5%AD%97%E7%AC%A6%E4%B8%B2/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2018/12/10/2018/go%E5%AD%97%E7%AC%A6%E4%B8%B2/" class="post-title-link" itemprop="url">go字符串容易忽略的地方</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2018-12-10 00:00:00" itemprop="dateCreated datePublished" datetime="2018-12-10T00:00:00+08:00">2018-12-10</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2024-11-20 16:34:57" itemprop="dateModified" datetime="2024-11-20T16:34:57+08:00">2024-11-20</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/go/" itemprop="url" rel="index"><span itemprop="name">go</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>字符串底层实际上是指向字节切片的指针,所以不管是赋值还是传参都只会拷贝其指针</p>
<h3 id="示例一:对切片的不当追加导致引用无法释放"><a href="#示例一:对切片的不当追加导致引用无法释放" class="headerlink" title="示例一:对切片的不当追加导致引用无法释放"></a>示例一:对切片的不当追加导致引用无法释放</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"runtime"</span></span><br><span class="line"> <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="comment">// 创建一个大字符串</span></span><br><span class="line"> bigStr := <span class="built_in">make</span>([]<span class="type">byte</span>, <span class="number">1024</span>*<span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="keyword">range</span> bigStr {</span><br><span class="line"> bigStr[i] = <span class="string">'a'</span></span><br><span class="line"> }</span><br><span class="line"> slice := []<span class="type">string</span>{}</span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> <span class="comment">// 将大字符串转换为字符串并追加到切片中</span></span><br><span class="line"> str := <span class="type">string</span>(bigStr)</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, str)</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(slice) > <span class="number">10000</span> {</span><br><span class="line"> <span class="comment">// 只是简单地重置切片长度,但原有元素的引用依然存在</span></span><br><span class="line"> slice = slice[:<span class="number">0</span>]</span><br><span class="line"> }</span><br><span class="line"> runtime.GC()</span><br><span class="line"> time.Sleep(time.Second)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>由于这些大字符串无法被垃圾回收器(GC)判定为不再使用(因为存在引用关系),随着循环不断进行,内存占用会持续增大,最终导致内存泄漏。正确的做法可以是当需要清理切片时,创建一个新的空切片重新赋值给 <code>slice</code> ,这样旧的底层数组没有了引用,就能被 GC 回收,比如 <code>slice = []string{}</code></p>
<h3 id="示例二:函数返回切片时引发的问题"><a href="#示例二:函数返回切片时引发的问题" class="headerlink" title="示例二:函数返回切片时引发的问题"></a>示例二:函数返回切片时引发的问题</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">package</span> main</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> (</span><br><span class="line"> <span class="string">"fmt"</span></span><br><span class="line"> <span class="string">"runtime"</span></span><br><span class="line"> <span class="string">"time"</span></span><br><span class="line">)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">GetStringSlice</span><span class="params">()</span></span> []<span class="type">string</span> {</span><br><span class="line"> slice := <span class="built_in">make</span>([]<span class="type">string</span>, <span class="number">0</span>)</span><br><span class="line"> <span class="keyword">for</span> i := <span class="number">0</span>; i < <span class="number">1000</span>; i++ {</span><br><span class="line"> bigStr := <span class="built_in">make</span>([]<span class="type">byte</span>, <span class="number">1024</span>)</span><br><span class="line"> <span class="keyword">for</span> j := <span class="keyword">range</span> bigStr {</span><br><span class="line"> bigStr[j] = <span class="string">'b'</span></span><br><span class="line"> }</span><br><span class="line"> str := <span class="type">string</span>(bigStr)</span><br><span class="line"> slice = <span class="built_in">append</span>(slice, str)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> slice</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">main</span><span class="params">()</span></span> {</span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> _ = GetStringSlice()</span><br><span class="line"> runtime.GC()</span><br><span class="line"> time.Sleep(time.Second)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<ul>
<li>在 <code>GetStringSlice</code> 函数中,创建了包含大量大字符串的切片并返回。每次调用这个函数时,虽然函数执行结束了,但返回的切片如果被外部变量接收(即使在示例中只是用 <code>_</code> 接收,实际情况可能会赋值给别的变量保存起来),其底层的字符串元素所占用的内存空间就一直被引用着。</li>
<li>假如调用该函数的频率很高,那么大量的内存空间无法被及时释放,就会造成内存泄漏。解决办法可以考虑在函数调用者那边合理地处理返回的切片,比如只在需要的短暂时间内持有切片,使用完后将其赋值为 <code>nil</code> ,让 GC 可以回收对应的内存,例如可以在合适的地方添加 <code>result := GetStringSlice(); defer func() { result = nil }()</code> (假设 <code>result</code> 是接收返回切片的变量)这样的逻辑。</li>
</ul>
<h5 id="示例三:超大字符串的截取"><a href="#示例三:超大字符串的截取" class="headerlink" title="示例三:超大字符串的截取"></a>示例三:超大字符串的截取</h5><p>如果对大量不同的超大字符串进行截取,我们就需要根据实际情况考虑这种字符串截取的内存成本问题,如果在超大字符串中截取一个小字符串,由于字符串的不可变性,拼接的动作会生成新的字符串副本,使得字符串的底层字节切片拷贝到了一个新的内存空间。这种方式夜之星进行一次内存拷贝,只会浪费一个字节的空间。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">s1 := (<span class="string">" "</span> + s[:<span class="number">20</span>])[<span class="number">1</span>:]</span><br></pre></td></tr></table></figure>
<h5 id="字节切片转换字符串会发生内存拷贝吗?"><a href="#字节切片转换字符串会发生内存拷贝吗?" class="headerlink" title="字节切片转换字符串会发生内存拷贝吗?"></a>字节切片转换字符串会发生内存拷贝吗?</h5><p>字节切片转字符串过程中会发生一次内存拷贝,这一点是我们需要留意的。字符串在转字节切片的过程是发生了内存拷贝的。</p>
<p>实际上,将字符串转换为字节切片 bytes ,会在内存中创建一个新的字节切片,并将字符串的内容复制到新的字节切片中。这样做是为了避免在字节切片中修改字符串的内容,因为字符串是不可变的。将字节切片s1强转成字符串s2同样也发生了内存拷贝。</p>
<p>如果要实现高效的字符串与字节切片之间的转换就需要减少或避免额外的内存分配操作。</p>
<p>可以通过使用 unsafe 包来实现字符串到字节切片的零拷贝转换。但是要注意,使用 unsafe 包是不安全的,因为它可以绕过 Go 语言的类型系统和内存安全检查。</p>
<p>在一般情况下,建议使用标准的字符串转换方法 <strong>[]byte(str)</strong> 来实现字符串到字节切片的转换,这样是安全且易于理解的方式。</p>
</div>
<footer class="post-footer">
<div class="post-eof"></div>
</footer>
</article>
<article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
<link itemprop="mainEntityOfPage" href="http://example.com/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/">
<span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
<meta itemprop="image" content="/images/avatar.gif">
<meta itemprop="name" content="WenQing">
<meta itemprop="description" content="">
</span>
<span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
<meta itemprop="name" content="WenQing">
</span>
<header class="post-header">
<h2 class="post-title" itemprop="name headline">
<a href="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/" class="post-title-link" itemprop="url">http</a>
</h2>
<div class="post-meta">
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar"></i>
</span>
<span class="post-meta-item-text">发表于</span>
<time title="创建时间:2018-11-11 00:00:00" itemprop="dateCreated datePublished" datetime="2018-11-11T00:00:00+08:00">2018-11-11</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-calendar-check"></i>
</span>
<span class="post-meta-item-text">更新于</span>
<time title="修改时间:2024-11-20 09:21:27" itemprop="dateModified" datetime="2024-11-20T09:21:27+08:00">2024-11-20</time>
</span>
<span class="post-meta-item">
<span class="post-meta-item-icon">
<i class="far fa-folder"></i>
</span>
<span class="post-meta-item-text">分类于</span>
<span itemprop="about" itemscope itemtype="http://schema.org/Thing">
<a href="/categories/http/" itemprop="url" rel="index"><span itemprop="name">http</span></a>
</span>
</span>
</div>
</header>
<div class="post-body" itemprop="articleBody">
<p>本篇文章来分析一下 Go 语言 HTTP 标准库是如何实现的。</p>
<blockquote>
<p>转载请声明出处哦~,本篇文章发布于luozhiyun的博客:<a target="_blank" rel="noopener" href="https://www.luozhiyun.com/archives/561">https://www.luozhiyun.com/archives/561</a></p>
<p>本文使用的go的源码1.15.7</p>
</blockquote>
<p>基于HTTP构建的服务标准模型包括两个端,客户端(<code>Client</code>)和服务端(<code>Server</code>)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以http服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。</p>
<p>一个典型的 HTTP 服务应该如图所示:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210147.png" class="" title="http">
<h2 id="HTTP-client"><a href="#HTTP-client" class="headerlink" title="HTTP client#"></a>HTTP client<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#2938538027">#</a></h2><p>在 Go 中可以直接通过 HTTP 包的 Get 方法来发起相关请求数据,一个简单例子:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc main() {</span><br><span class="line"> resp, err := http.Get(<span class="string">"http://httpbin.org/get?name=luozhiyun&age=27"</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> fmt.Println(err)</span><br><span class="line"> <span class="keyword">return</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">defer</span> resp.Body.Close()</span><br><span class="line"> body, _ := ioutil.ReadAll(resp.Body)</span><br><span class="line"> fmt.Println(<span class="type">string</span>(body))</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们下面通过这个例子来进行分析。</p>
<p>HTTP 的 Get 方法会调用到 DefaultClient 的 Get 方法,DefaultClient 是 Client 的一个空实例,所以最后会调用到 Client 的 Get 方法:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210156.png" class="" title="httpclient">
<h3 id="Client-结构体"><a href="#Client-结构体" class="headerlink" title="Client 结构体#"></a>Client 结构体<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#3415504085">#</a></h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Copytype Client <span class="keyword">struct</span> { </span><br><span class="line"> Transport RoundTripper </span><br><span class="line"> CheckRedirect <span class="function"><span class="keyword">func</span><span class="params">(req *Request, via []*Request)</span></span> <span class="type">error</span> </span><br><span class="line"> Jar CookieJar </span><br><span class="line"> Timeout time.Duration</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>Client 结构体总共由四个字段组成:</p>
<p><strong>Transport</strong>:表示 HTTP 事务,用于处理客户端的请求连接并等待服务端的响应;</p>
<p><strong>CheckRedirect</strong>:用于指定处理重定向的策略;</p>
<p><strong>Jar</strong>:用于管理和存储请求中的 cookie;</p>
<p><strong>Timeout</strong>:指定客户端请求的最大超时时间,该超时时间包括连接、任何的重定向以及读取相应的时间;</p>
<h3 id="初始化请求"><a href="#初始化请求" class="headerlink" title="初始化请求#"></a>初始化请求<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#154025721">#</a></h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (c *Client) Get(url <span class="type">string</span>) (resp *Response, err <span class="type">error</span>) {</span><br><span class="line"> <span class="comment">// 根据方法名、URL 和请求体构建请求</span></span><br><span class="line"> req, err := NewRequest(<span class="string">"GET"</span>, url, <span class="literal">nil</span>)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 执行请求</span></span><br><span class="line"> <span class="keyword">return</span> c.Do(req)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>我们要发起一个请求首先需要根据请求类型构建一个完整的请求头、请求体、请求参数。然后才是根据请求的完整结构来执行请求。</p>
<h4 id="NewRequest-初始化请求"><a href="#NewRequest-初始化请求" class="headerlink" title="NewRequest 初始化请求#"></a>NewRequest 初始化请求<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#4173754478">#</a></h4><p>NewRequest 会调用到 NewRequestWithContext 函数上。这个函数会根据请求返回一个 Request 结构体,它里面包含了一个 HTTP 请求所有信息。</p>
<p><strong>Request</strong></p>
<p>Request 结构体有很多字段,我这里列举几个大家比较熟悉的字段:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210223.png" class="" title="httpclient2">
<p><strong>NewRequestWithContext</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc NewRequestWithContext(ctx context.Context, method, url <span class="type">string</span>, body io.Reader) (*Request, <span class="type">error</span>) {</span><br><span class="line"> ...</span><br><span class="line"> <span class="comment">// parse url</span></span><br><span class="line"> u, err := urlpkg.Parse(url)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line"> }</span><br><span class="line"> rc, ok := body.(io.ReadCloser)</span><br><span class="line"> <span class="keyword">if</span> !ok && body != <span class="literal">nil</span> {</span><br><span class="line"> rc = ioutil.NopCloser(body)</span><br><span class="line"> } </span><br><span class="line"> u.Host = removeEmptyPort(u.Host)</span><br><span class="line"> req := &Request{</span><br><span class="line"> ctx: ctx,</span><br><span class="line"> Method: method,</span><br><span class="line"> URL: u,</span><br><span class="line"> Proto: <span class="string">"HTTP/1.1"</span>,</span><br><span class="line"> ProtoMajor: <span class="number">1</span>,</span><br><span class="line"> ProtoMinor: <span class="number">1</span>,</span><br><span class="line"> Header: <span class="built_in">make</span>(Header),</span><br><span class="line"> Body: rc,</span><br><span class="line"> Host: u.Host,</span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> req, <span class="literal">nil</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>NewRequestWithContext 函数会将请求封装成一个 Request 结构体并返回。</p>
<h3 id="准备-http-发送请求"><a href="#准备-http-发送请求" class="headerlink" title="准备 http 发送请求#"></a>准备 http 发送请求<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#3651912035">#</a></h3><img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210229.png" class="" title="httpclientsend">
<p>如上图所示,Client 调用 Do 方法处理发送请求最后会调用到 send 函数中。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout <span class="function"><span class="keyword">func</span><span class="params">()</span></span> <span class="type">bool</span>, err <span class="type">error</span>) {</span><br><span class="line"> resp, didTimeout, err = send(req, c.transport(), deadline)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, didTimeout, err</span><br><span class="line"> }</span><br><span class="line"> ...</span><br><span class="line"> <span class="keyword">return</span> resp, <span class="literal">nil</span>, <span class="literal">nil</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<h4 id="Transport"><a href="#Transport" class="headerlink" title="Transport#"></a>Transport<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#542498769">#</a></h4><p>Client 的 send 方法在调用 send 函数进行下一步的处理前会先调用 transport 方法获取 DefaultTransport 实例,该实例如下:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">Copyvar DefaultTransport RoundTripper = &Transport{</span><br><span class="line"> <span class="comment">// 定义 HTTP 代理策略</span></span><br><span class="line"> Proxy: ProxyFromEnvironment,</span><br><span class="line"> DialContext: (&net.Dialer{</span><br><span class="line"> Timeout: <span class="number">30</span> * time.Second,</span><br><span class="line"> KeepAlive: <span class="number">30</span> * time.Second,</span><br><span class="line"> DualStack: <span class="literal">true</span>,</span><br><span class="line"> }).DialContext,</span><br><span class="line"> ForceAttemptHTTP2: <span class="literal">true</span>,</span><br><span class="line"> <span class="comment">// 最大空闲连接数</span></span><br><span class="line"> MaxIdleConns: <span class="number">100</span>,</span><br><span class="line"> <span class="comment">// 空闲连接超时时间</span></span><br><span class="line"> IdleConnTimeout: <span class="number">90</span> * time.Second,</span><br><span class="line"> <span class="comment">// TLS 握手超时时间</span></span><br><span class="line"> TLSHandshakeTimeout: <span class="number">10</span> * time.Second,</span><br><span class="line"> ExpectContinueTimeout: <span class="number">1</span> * time.Second,</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210234.png" class="" title="transport">
<p>Transport 实现 RoundTripper 接口,该结构体会发送 http 请求并等待响应。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Copytype RoundTripper <span class="keyword">interface</span> { </span><br><span class="line"> RoundTrip(*Request) (*Response, <span class="type">error</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>从 RoundTripper 接口我们也可以看出,该接口定义的 RoundTrip 方法会具体的处理请求,处理完毕之后会响应 Response。</p>
<p>回到我们上面的 Client 的 send 方法中,它会调用 send 函数,这个函数主要逻辑都交给 Transport 的 RoundTrip 方法来执行。</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210244.png" class="" title="transport2">
<p>RoundTrip 会调用到 roundTrip 方法中:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) roundTrip(req *Request) (*Response, <span class="type">error</span>) {</span><br><span class="line"> t.nextProtoOnce.Do(t.onceSetNextProtoDefaults)</span><br><span class="line"> ctx := req.Context()</span><br><span class="line"> trace := httptrace.ContextClientTrace(ctx) </span><br><span class="line"> ... </span><br><span class="line"> <span class="keyword">for</span> {</span><br><span class="line"> <span class="keyword">select</span> {</span><br><span class="line"> <span class="keyword">case</span> <-ctx.Done():</span><br><span class="line"> req.closeBody()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, ctx.Err()</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 封装请求</span></span><br><span class="line"> treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey} </span><br><span class="line"> cm, err := t.connectMethodForRequest(treq)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> req.closeBody()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line"> } </span><br><span class="line"> <span class="comment">// 获取连接</span></span><br><span class="line"> pconn, err := t.getConn(treq, cm)</span><br><span class="line"> <span class="keyword">if</span> err != <span class="literal">nil</span> {</span><br><span class="line"> t.setReqCanceler(cancelKey, <span class="literal">nil</span>)</span><br><span class="line"> req.closeBody()</span><br><span class="line"> <span class="keyword">return</span> <span class="literal">nil</span>, err</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 等待响应结果</span></span><br><span class="line"> <span class="keyword">var</span> resp *Response</span><br><span class="line"> <span class="keyword">if</span> pconn.alt != <span class="literal">nil</span> {</span><br><span class="line"> <span class="comment">// HTTP/2 path.</span></span><br><span class="line"> t.setReqCanceler(cancelKey, <span class="literal">nil</span>) <span class="comment">// not cancelable with CancelRequest</span></span><br><span class="line"> resp, err = pconn.alt.RoundTrip(req)</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> resp, err = pconn.roundTrip(treq)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> err == <span class="literal">nil</span> {</span><br><span class="line"> resp.Request = origReq</span><br><span class="line"> <span class="keyword">return</span> resp, <span class="literal">nil</span></span><br><span class="line"> } </span><br><span class="line"> ...</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>roundTrip 方法会做两件事情:</p>
<ol>
<li>调用 Transport 的 getConn 方法获取连接;</li>
<li>在获取到连接后,调用 persistConn 的 roundTrip 方法等待请求响应结果;</li>
</ol>
<h3 id="获取连接-getConn"><a href="#获取连接-getConn" class="headerlink" title="获取连接 getConn#"></a>获取连接 getConn<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#221648875">#</a></h3><p>getConn 有两个阶段:</p>
<ol>
<li>调用 queueForIdleConn 获取空闲 connection;</li>
<li>调用 queueForDial 等待创建新的 connection;</li>
</ol>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210250.png" class="" title="getconn4">
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err <span class="type">error</span>) { req := treq.Request trace := treq.trace ctx := req.Context() <span class="keyword">if</span> trace != <span class="literal">nil</span> && trace.GetConn != <span class="literal">nil</span> { trace.GetConn(cm.addr()) } <span class="comment">// 将请求封装成 wantConn 结构体 w := &wantConn{ cm: cm, key: cm.key(), ctx: ctx, ready: make(chan struct{}, 1), beforeDial: testHookPrePendingDial, afterDial: testHookPostPendingDial, } defer func() { if err != nil { w.cancel(t, err) } }() // 获取空闲连接 if delivered := t.queueForIdleConn(w); delivered { pc := w.pc ... t.setReqCanceler(treq.cancelKey, func(error) {}) return pc, nil } // 创建连接 t.queueForDial(w) select { // 获取到连接后进入该分支 case <-w.ready: ... return w.pc, w.err ...}</span></span><br></pre></td></tr></table></figure>
<h4 id="获取空闲连接-queueForIdleConn"><a href="#获取空闲连接-queueForIdleConn" class="headerlink" title="获取空闲连接 queueForIdleConn#"></a>获取空闲连接 queueForIdleConn<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#494717711">#</a></h4><p>成功获取到空闲 connection:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210254.png" class="" title="getconn">
<p>成功获取 connection 分为如下几步:</p>
<ol>
<li>根据当前的请求的地址去<strong>空闲 connection 字典</strong>中查看存不存在空闲的 connection 列表;</li>
<li>如果能获取到空闲的 connection 列表,那么获取到列表的最后一个 connection;</li>
<li>返回;</li>
</ol>
<p>获取不到空闲 connection:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210258.png" class="" title="getconn2">
<p>当获取不到空闲 connection 时:</p>
<ol>
<li>根据当前的请求的地址去<strong>空闲 connection 字典</strong>中查看存不存在空闲的 connection 列表;</li>
<li>不存在该请求的 connection 列表,那么将该 wantConn 加入到 <strong>等待获取空闲 connection 字典</strong>中;</li>
</ol>
<p>从上面的图解应该就很能看出这一步会怎么操作了,这里简要的分析一下代码,让大家更清楚里面的逻辑:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) queueForIdleConn(w *wantConn) (delivered <span class="type">bool</span>) { <span class="keyword">if</span> t.DisableKeepAlives { <span class="keyword">return</span> <span class="literal">false</span> } t.idleMu.Lock() <span class="keyword">defer</span> t.idleMu.Unlock() t.closeIdle = <span class="literal">false</span> <span class="keyword">if</span> w == <span class="literal">nil</span> { <span class="keyword">return</span> <span class="literal">false</span> } <span class="comment">// 计算空闲连接超时时间 var oldTime time.Time if t.IdleConnTimeout > 0 { oldTime = time.Now().Add(-t.IdleConnTimeout) } // Look for most recently-used idle connection. // 找到key相同的 connection 列表 if list, ok := t.idleConn[w.key]; ok { stop := false delivered := false for len(list) > 0 && !stop { // 找到connection列表最后一个 pconn := list[len(list)-1] // 检查这个 connection 是不是等待太久了 tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime) if tooOld { go pconn.closeConnIfStillIdle() } // 该 connection 被标记为 broken 或 闲置太久 continue if pconn.isBroken() || tooOld { list = list[:len(list)-1] continue } // 尝试将该 connection 写入到 w 中 delivered = w.tryDeliver(pconn, nil) if delivered { // 操作成功,需要将 connection 从空闲列表中移除 if pconn.alt != nil { } else { t.idleLRU.remove(pconn) list = list[:len(list)-1] } } stop = true } if len(list) > 0 { t.idleConn[w.key] = list } else { // 如果该 key 对应的空闲列表不存在,那么将该key从字典中移除 delete(t.idleConn, w.key) } if stop { return delivered } } // 如果找不到空闲的 connection if t.idleConnWait == nil { t.idleConnWait = make(map[connectMethodKey]wantConnQueue) } // 将该 wantConn 加入到 等待获取空闲 connection 字典中 q := t.idleConnWait[w.key] q.cleanFront() q.pushBack(w) t.idleConnWait[w.key] = q return false}</span></span><br></pre></td></tr></table></figure>
<p>上面的注释已经很清楚了,我这里就不再解释了。</p>
<h4 id="建立连接-queueForDial"><a href="#建立连接-queueForDial" class="headerlink" title="建立连接 queueForDial#"></a>建立连接 queueForDial<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#2138196219">#</a></h4><img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210303.png" class="" title="getconn3">
<p>在获取不到空闲连接之后,会尝试去建立连接,从上面的图大致可以看到,总共分为以下几个步骤:</p>
<ol>
<li>在调用 queueForDial 方法的时候会校验 MaxConnsPerHost 是否未设置或已达上限;<ol>
<li>检验不通过则将当前的请求放入到 connsPerHostWait 等待字典中;</li>
</ol>
</li>
<li>如果校验通过那么会异步的调用 dialConnFor 方法创建连接;</li>
<li>dialConnFor 方法首先会调用 dialConn 方法创建 TCP 连接,然后启动两个异步线程来处理读写数据,然后调用 tryDeliver 将连接绑定到 wantConn 上面。</li>
</ol>
<p>下面进行代码分析:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) queueForDial(w *wantConn) { w.beforeDial() <span class="comment">// 小于零说明无限制,异步建立连接 if t.MaxConnsPerHost <= 0 { go t.dialConnFor(w) return } t.connsPerHostMu.Lock() defer t.connsPerHostMu.Unlock() // 每个 host 建立的连接数没达到上限,异步建立连接 if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost { if t.connsPerHost == nil { t.connsPerHost = make(map[connectMethodKey]int) } t.connsPerHost[w.key] = n + 1 go t.dialConnFor(w) return } //每个 host 建立的连接数已达到上限,需要进入等待队列 if t.connsPerHostWait == nil { t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue) } q := t.connsPerHostWait[w.key] q.cleanFront() q.pushBack(w) t.connsPerHostWait[w.key] = q}</span></span><br></pre></td></tr></table></figure>
<p>这里主要进行参数校验,如果最大连接数限制为零,亦或是每个 host 建立的连接数没达到上限,那么直接异步建立连接。</p>
<p><strong>dialConnFor</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) dialConnFor(w *wantConn) { <span class="keyword">defer</span> w.afterDial() <span class="comment">// 建立连接 pc, err := t.dialConn(w.ctx, w.cm) // 连接绑定 wantConn delivered := w.tryDeliver(pc, err) // 建立连接成功,但是绑定 wantConn 失败 // 那么将该连接放置到空闲连接字典或调用 等待获取空闲 connection 字典 中的元素执行 if err == nil && (!delivered || pc.alt != nil) { t.putOrCloseIdleConn(pc) } if err != nil { t.decConnsPerHost(w.key) }}</span></span><br></pre></td></tr></table></figure>
<p>dialConnFor 会调用 dialConn 进行 TCP 连接创建,创建完毕之后调用 tryDeliver 方法和 wantConn 进行绑定。</p>
<p><strong>dialConn</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err <span class="type">error</span>) { <span class="comment">// 创建连接结构体 pconn = &persistConn{ t: t, cacheKey: cm.key(), reqch: make(chan requestAndChan, 1), writech: make(chan writeRequest, 1), closech: make(chan struct{}), writeErrCh: make(chan error, 1), writeLoopDone: make(chan struct{}), } ... if cm.scheme() == "https" && t.hasCustomTLSDialer() { ... } else { // 建立 tcp 连接 conn, err := t.dial(ctx, "tcp", cm.addr()) if err != nil { return nil, wrapErr(err) } pconn.conn = conn } ... if s := pconn.tlsState; s != nil && s.NegotiatedProtocolIsMutual && s.NegotiatedProtocol != "" { if next, ok := t.TLSNextProto[s.NegotiatedProtocol]; ok { alt := next(cm.targetAddr, pconn.conn.(*tls.Conn)) if e, ok := alt.(http2erringRoundTripper); ok { // pconn.conn was closed by next (http2configureTransport.upgradeFn). return nil, e.err } return &persistConn{t: t, cacheKey: pconn.cacheKey, alt: alt}, nil } } pconn.br = bufio.NewReaderSize(pconn, t.readBufferSize()) pconn.bw = bufio.NewWriterSize(persistConnWriter{pconn}, t.writeBufferSize()) //为每个连接异步处理读写数据 go pconn.readLoop() go pconn.writeLoop() return pconn, nil}</span></span><br></pre></td></tr></table></figure>
<p>这里会根据 schema 的不同设置不同的连接配置,我上面显示的是我们常用的 HTTP 连接的创建过程。对于 HTTP 来说会建立 tcp 连接,然后为连接异步处理读写数据,最后将创建好的连接返回。</p>
<h3 id="等待响应"><a href="#等待响应" class="headerlink" title="等待响应#"></a>等待响应<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#2910995870">#</a></h3><p>这一部分的内容会稍微复杂一些,但确实非常的有趣。</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210309.png" class="" title="response">
<p>在创建连接的时候会初始化两个 channel :writech 负责写入请求数据,reqch负责读取响应数据。我们在上面创建连接的时候,也提到了会为连接创建两个异步循环 readLoop 和 writeLoop 来负责处理读写数据。</p>
<p>在获取到连接之后,会调用连接的 roundTrip 方法,它首先会将请求数据写入到 writech 管道中,writeLoop 接收到数据之后就会处理请求。</p>
<p>然后 roundTrip 会将 requestAndChan 结构体写入到 reqch 管道中,然后 roundTrip 会循环等待。readLoop 读取到响应数据之后就会通过 requestAndChan 结构体中保存的管道将数据封装成 responseAndError 结构体回写,这样 roundTrip 就可以接受到响应数据结束循环等待并返回。</p>
<p><strong>roundTrip</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err <span class="type">error</span>) { ... writeErrCh := <span class="built_in">make</span>(<span class="keyword">chan</span> <span class="type">error</span>, <span class="number">1</span>) <span class="comment">// 将请求数据写入到 writech 管道中 pc.writech <- writeRequest{req, writeErrCh, continueCh} // 用于接收响应的管道 resc := make(chan responseAndError) // 将用于接收响应的管道封装成 requestAndChan 写入到 reqch 管道中 pc.reqch <- requestAndChan{ req: req.Request, cancelKey: req.cancelKey, ch: resc, ... } ... for { testHookWaitResLoop() select { // 接收到响应数据 case re := <-resc: if (re.res == nil) == (re.err == nil) { panic(fmt.Sprintf("internal error: exactly one of res or err should be set; nil=%v", re.res == nil)) } if debugRoundTrip { req.logf("resc recv: %p, %T/%#v", re.res, re.err, re.err) } if re.err != nil { return nil, pc.mapRoundTripError(req, startBytesWritten, re.err) } // 返回响应数据 return re.res, nil ... }}</span></span><br></pre></td></tr></table></figure>
<p>这里会封装好 writeRequest 作为发送请求的数据,并将用于接收响应的管道封装成 requestAndChan 写入到 reqch 管道中,然后循环等待接受响应。</p>
<p>然后 writeLoop 会进行请求数据 writeRequest :</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (pc *persistConn) writeLoop() { <span class="keyword">defer</span> <span class="built_in">close</span>(pc.writeLoopDone) <span class="keyword">for</span> { <span class="keyword">select</span> { <span class="keyword">case</span> wr := <-pc.writech: startBytesWritten := pc.nwrite <span class="comment">// 向 TCP 连接中写入数据,并发送至目标服务器 err := wr.req.Request.write(pc.bw, pc.isProxy, wr.req.extra, pc.waitForContinue(wr.continueCh)) ... case <-pc.closech: return } }}</span></span><br></pre></td></tr></table></figure>
<p>这里会将从 writech 管道中获取到的数据写入到 TCP 连接中,并发送至目标服务器。</p>
<p><strong>readLoop</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (pc *persistConn) readLoop() { closeErr := errReadLoopExiting <span class="comment">// default value, if not changed below defer func() { pc.close(closeErr) pc.t.removeIdleConn(pc) }() ... alive := true for alive { pc.readLimit = pc.maxHeaderResponseSize() // 获取 roundTrip 发送的结构体 rc := <-pc.reqch trace := httptrace.ContextClientTrace(rc.req.Context()) var resp *Response if err == nil { // 读取数据 resp, err = pc.readResponse(rc, trace) } else { err = transportReadFromServerError{err} closeErr = err } ... // 将响应数据写回到管道中 select { case rc.ch <- responseAndError{res: resp}: case <-rc.callerGone: return } ... }}</span></span><br></pre></td></tr></table></figure>
<p>这里是从 TCP 连接中读取到对应的请求响应数据,通过 roundTrip 传入的管道再回写,然后 roundTrip 就会接受到数据并获取的响应数据返回。</p>
<h2 id="http-server"><a href="#http-server" class="headerlink" title="http server#"></a>http server<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#2101551938">#</a></h2><p>我这里继续以一个简单的例子作为开头:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc HelloHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, <span class="string">"Hello World"</span>)}<span class="function"><span class="keyword">func</span> <span class="title">main</span> <span class="params">()</span></span> { http.HandleFunc(<span class="string">"/"</span>, HelloHandler) http.ListenAndServe(<span class="string">":8000"</span>, <span class="literal">nil</span>)}</span><br></pre></td></tr></table></figure>
<p>在实现上面我先用一张图进行简要的介绍一下:</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210316.png" class="" title="server2">
<p>其实我们从上面例子的方法名就可以知道一些大致的步骤:</p>
<ol>
<li>注册处理器到一个 hash 表中,可以通过键值路由匹配;</li>
<li>注册完之后就是开启循环监听,每监听到一个连接就会创建一个 Goroutine;</li>
<li>在创建好的 Goroutine 里面会循环的等待接收请求数据,然后根据请求的地址去处理器路由表中匹配对应的处理器,然后将请求交给处理器处理;</li>
</ol>
<h3 id="注册处理器"><a href="#注册处理器" class="headerlink" title="注册处理器#"></a>注册处理器<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#3100560689">#</a></h3><p>处理器的注册如上面的例子所示,是通过调用 HandleFunc 函数来实现的。</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210320.png" class="" title="server">
<p>HandleFunc 函数会一直调用到 ServeMux 的 Handle 方法中。</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (mux *ServeMux) Handle(pattern <span class="type">string</span>, handler Handler) { mux.mu.Lock() <span class="keyword">defer</span> mux.mu.Unlock() ... e := muxEntry{h: handler, pattern: pattern} mux.m[pattern] = e <span class="keyword">if</span> pattern[<span class="built_in">len</span>(pattern)<span class="number">-1</span>] == <span class="string">'/'</span> { mux.es = appendSorted(mux.es, e) } <span class="keyword">if</span> pattern[<span class="number">0</span>] != <span class="string">'/'</span> { mux.hosts = <span class="literal">true</span> }}</span><br></pre></td></tr></table></figure>
<p>Handle 会根据路由作为 hash 表的键来保存 <code>muxEntry</code> 对象,<code>muxEntry</code>封装了 pattern 和 handler。如果路由表达式以<code>'/'</code>结尾,则将对应的<code>muxEntry</code>对象加入到<code>[]muxEntry</code>中。</p>
<p>hash 表是用于路由精确匹配,<code>[]muxEntry</code>用于部分匹配。</p>
<h3 id="监听"><a href="#监听" class="headerlink" title="监听#"></a>监听<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#2398140697">#</a></h3><p>监听是通过调用 ListenAndServe 函数,里面会调用 server 的 ListenAndServe 方法:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (srv *Server) ListenAndServe() <span class="type">error</span> { <span class="keyword">if</span> srv.shuttingDown() { <span class="keyword">return</span> ErrServerClosed } addr := srv.Addr <span class="keyword">if</span> addr == <span class="string">""</span> { addr = <span class="string">":http"</span> } <span class="comment">// 监听端口 ln, err := net.Listen("tcp", addr) if err != nil { return err } // 循环接收监听到的网络请求 return srv.Serve(ln)}</span></span><br></pre></td></tr></table></figure>
<p><strong>Serve</strong></p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (srv *Server) Serve(l net.Listener) <span class="type">error</span> { ... baseCtx := context.Background() ctx := context.WithValue(baseCtx, ServerContextKey, srv) <span class="keyword">for</span> { <span class="comment">// 接收 listener 过来的网络连接 rw, err := l.Accept() ... tempDelay = 0 c := srv.newConn(rw) c.setState(c.rwc, StateNew) // 创建协程处理连接 go c.serve(connCtx) }}</span></span><br></pre></td></tr></table></figure>
<p>Serve 这个方法里面会用一个循环去接收监听到的网络连接,然后创建协程处理连接。所以难免就会有一个问题,如果并发很高的话,可能会一次性创建太多协程,导致处理不过来的情况。</p>
<h3 id="处理请求"><a href="#处理请求" class="headerlink" title="处理请求#"></a>处理请求<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#612431917">#</a></h3><p>处理请求是通过为每个连接创建 goroutine 来处理对应的请求:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (c *conn) serve(ctx context.Context) { c.remoteAddr = c.rwc.RemoteAddr().String() ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) ... ctx, cancelCtx := context.WithCancel(ctx) c.cancelCtx = cancelCtx <span class="keyword">defer</span> cancelCtx() c.r = &connReader{conn: c} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, <span class="number">4</span><<<span class="number">10</span>) <span class="keyword">for</span> { <span class="comment">// 读取请求 w, err := c.readRequest(ctx) ... // 根据请求路由调用处理器处理请求 serverHandler{c.server}.ServeHTTP(w, w.req) w.cancelCtx() if c.hijacked() { return } w.finishRequest() ... }}</span></span><br></pre></td></tr></table></figure>
<p>当一个连接建立之后,该连接中所有的请求都将在这个协程中进行处理,直到连接被关闭。在 for 循环里面会循环调用 readRequest 读取请求进行处理。</p>
<p>请求处理是通过调用 ServeHTTP 进行的:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copytype serverHandler <span class="keyword">struct</span> { srv *Server}<span class="function"><span class="keyword">func</span> <span class="params">(sh serverHandler)</span></span> ServeHTTP(rw ResponseWriter, req *Request) { handler := sh.srv.Handler <span class="keyword">if</span> handler == <span class="literal">nil</span> { handler = DefaultServeMux } <span class="keyword">if</span> req.RequestURI == <span class="string">"*"</span> && req.Method == <span class="string">"OPTIONS"</span> { handler = globalOptionsHandler{} } handler.ServeHTTP(rw, req)}</span><br></pre></td></tr></table></figure>
<p>serverHandler 其实就是 Server 包装了一层。这里的 <code>sh.srv.Handler</code>参数实际上是传入的 ServeMux 实例,所以这里最后会调用到 ServeMux 的 ServeHTTP 方法。</p>
<img src="/2018/11/11/2018/go_http%E8%A7%A3%E6%9E%90/20210608210326.png" class="" title="response2">
<p>最终会通过 handler 调用到 match 方法进行路由匹配:</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Copyfunc (mux *ServeMux) match(path <span class="type">string</span>) (h Handler, pattern <span class="type">string</span>) { v, ok := mux.m[path] <span class="keyword">if</span> ok { <span class="keyword">return</span> v.h, v.pattern } <span class="keyword">for</span> _, e := <span class="keyword">range</span> mux.es { <span class="keyword">if</span> strings.HasPrefix(path, e.pattern) { <span class="keyword">return</span> e.h, e.pattern } } <span class="keyword">return</span> <span class="literal">nil</span>, <span class="string">""</span>}</span><br></pre></td></tr></table></figure>
<p>这个方法里首先会利用进行精确匹配,如果匹配成功那么直接返回;匹配不成功,那么会根据 <code>[]muxEntry</code>中保存的和当前路由最接近的已注册的父节点路由进行匹配,否则继续匹配下一个父节点路由,直到根路由<code>/</code>。最后会调用对应的处理器进行处理。</p>
<h2 id="Reference"><a href="#Reference" class="headerlink" title="Reference#"></a>Reference<a target="_blank" rel="noopener" href="https://www.cnblogs.com/luozhiyun/p/14954558.html#4042723866">#</a></h2><p><a target="_blank" rel="noopener" href="https://cloud.tencent.com/developer/article/1515297">https://cloud.tencent.com/developer/article/1515297</a></p>
<p><a target="_blank" rel="noopener" href="https://duyanghao.github.io/http-transport/">https://duyanghao.github.io/http-transport/</a></p>
<p><a target="_blank" rel="noopener" href="https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http">https://draveness.me/golang/docs/part4-advanced/ch09-stdlib/golang-net-http</a></p>
<p><a target="_blank" rel="noopener" href="https://laravelacademy.org/post/21003">https://laravelacademy.org/post/21003</a></p>
<p><a target="_blank" rel="noopener" href="https://segmentfault.com/a/1190000021653550">https://segmentfault.com/a/1190000021653550</a></p>
</div>