-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
561 lines (301 loc) · 222 KB
/
atom.xml
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>千里一日还先森</title>
<link href="/atom.xml" rel="self"/>
<link href="http://yoursite.com/"/>
<updated>2019-10-15T06:14:36.338Z</updated>
<id>http://yoursite.com/</id>
<author>
<name>Turbolento</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>VUEjs前端+tornado后端优化开发调试</title>
<link href="http://yoursite.com/2019/10/15/VUEjs%E5%89%8D%E7%AB%AF-tornado%E5%90%8E%E7%AB%AF%E4%BC%98%E5%8C%96%E5%BC%80%E5%8F%91%E8%B0%83%E8%AF%95/"/>
<id>http://yoursite.com/2019/10/15/VUEjs前端-tornado后端优化开发调试/</id>
<published>2019-10-15T02:45:49.000Z</published>
<updated>2019-10-15T06:14:36.338Z</updated>
<content type="html"><![CDATA[<p>在开发WEB前后端分离的应用时,在开发环境中,前后端应用访问的主机地址(ip:port)不同;但在生产环境,一般都会使用nginx代理前后端应用使得前后端的访问地址为同一个。所以,前后端应用在不同环境中的某些行为也不相同。比如,在开发环境中,由于浏览器的同源策略,导致前端的ajax请求受限——<i>“No ‘Access-Control-Allow-Origin’ header is present on the requested resource. </i>解决这个问题,只需要在后端应用返回的headers里面加上响应头:<code>Access-Control-Allow-Origin: *</code>。但由于nginx代理,生产环境中前后端主机地址一致,为了提高安全,后端并不需要这个设置,所以后端应该根据开发环境做不同的处理。</p><h4 id="0x01-前端处理"><a href="#0x01-前端处理" class="headerlink" title="0x01 前端处理"></a>0x01 前端处理</h4><p>1. 定义函数 <code>isDev</code> 判断是否是开发环境,在/src/main.js中增加如下代码:<br><figure class="highlight javascript"><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">Vue.prototype.isDev = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>{</span><br><span class="line"> <span class="keyword">return</span> process.env.NODE_ENV == <span class="string">"development"</span> ? <span class="literal">true</span> : <span class="literal">false</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>2. 根据环境不同,获取相应的前后端地址,在/src/main.js中增加如下代码:<br><figure class="highlight javascript"><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">Vue.prototype.getFrontendUrl = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> frontend_url = <span class="keyword">this</span>.isDev() ? <span class="string">"/"</span> : <span class="string">"/applicationName/"</span></span><br><span class="line"> <span class="keyword">return</span> frontend_url</span><br><span class="line">}</span><br><span class="line">Vue.prototype.getBackendUrl = <span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="keyword">var</span> runenv = process.env.NODE_ENV</span><br><span class="line"> <span class="keyword">var</span> backend_url = <span class="keyword">this</span>.isDev() ? <span class="string">"http://localhost:8000/"</span> : <span class="string">"/"</span></span><br><span class="line"> <span class="keyword">return</span> backend_url</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>3. ajax请求:<br><figure class="highlight javascript"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> x = {}</span><br><span class="line"><span class="keyword">var</span> backend_url = <span class="keyword">this</span>.getBackendUrl()</span><br><span class="line"><span class="keyword">var</span> forbidden = <span class="literal">false</span></span><br><span class="line"><span class="keyword">var</span> connectError = <span class="literal">false</span></span><br><span class="line">$.ajax({</span><br><span class="line"> url: backend_url + <span class="string">"disguiserApi/delapi?aid="</span> + <span class="keyword">this</span>.aid,</span><br><span class="line"> dataType: <span class="string">"JSON"</span>,</span><br><span class="line"> <span class="keyword">async</span>: <span class="literal">false</span>,</span><br><span class="line"> success: <span class="function"><span class="keyword">function</span> (<span class="params">ret</span>) </span>{</span><br><span class="line"> x = ret</span><br><span class="line"> },</span><br><span class="line"> error: <span class="function"><span class="keyword">function</span> (<span class="params">e</span>) </span>{</span><br><span class="line"> <span class="keyword">if</span>(e.status==<span class="number">403</span>){</span><br><span class="line"> forbidden = <span class="literal">true</span></span><br><span class="line"> }<span class="keyword">else</span>{</span><br><span class="line"> connectError = <span class="literal">true</span></span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">})</span><br><span class="line"><span class="keyword">if</span> (connectError) {</span><br><span class="line"> <span class="keyword">this</span>.$message.error(<span class="string">'服务器异常!'</span>);</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span>(forbidden){</span><br><span class="line"> <span class="keyword">var</span> frontend = <span class="keyword">this</span>.getFrontendUrl()</span><br><span class="line"> <span class="keyword">var</span> next = <span class="built_in">encodeURIComponent</span>(<span class="built_in">window</span>.location.pathname.substr(<span class="number">1</span>) + <span class="built_in">window</span>.location.search)</span><br><span class="line"> <span class="built_in">window</span>.location.href = frontend + <span class="string">"login?next="</span>+next</span><br><span class="line">} <span class="keyword">else</span> <span class="keyword">if</span> (x.status != <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">this</span>.$message.error(x.msg||<span class="string">"未知异常"</span>)</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">var</span> frontendurl = <span class="keyword">this</span>.frontend</span><br><span class="line"> <span class="keyword">this</span>.$message({</span><br><span class="line"> type: <span class="string">'success'</span>,</span><br><span class="line"> message: <span class="string">'删除成功!'</span></span><br><span class="line"> });</span><br><span class="line"> setTimeout(<span class="function"><span class="keyword">function</span> (<span class="params"></span>) </span>{</span><br><span class="line"> <span class="built_in">window</span>.location.href = frontendurl + <span class="string">"dashboard?sid="</span> + <span class="keyword">this</span>.systemInfo.sysid</span><br><span class="line"> }, <span class="number">1500</span>)</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h4 id="0x02-后端处理"><a href="#0x02-后端处理" class="headerlink" title="0x02 后端处理"></a>0x02 后端处理</h4><p>1. 启动应用时,根据命令参数设置环境,默认为prod环境:<br><figure class="highlight python"><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"><span class="string">'''1) file: setting.py'''</span></span><br><span class="line">ENV = <span class="string">"prod"</span></span><br><span class="line"></span><br><span class="line"><span class="string">'''2) file: server.py'''</span></span><br><span class="line"><span class="comment">#启用开发环境 python server.py dev</span></span><br><span class="line"><span class="keyword">from</span> setting <span class="keyword">import</span> settings</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> len(sys.argv) == <span class="number">2</span> <span class="keyword">and</span> sys.argv[<span class="number">1</span>] == <span class="string">"dev"</span>:</span><br><span class="line"> setting.ENV = <span class="string">"dev"</span></span><br></pre></td></tr></table></figure></p><p>2. 开发环境不对用户做登录认证:<br><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="string">'''参照tornado.web.authenticated()修饰器函数,不能直接使用该修饰器,</span></span><br><span class="line"><span class="string">因为该修饰器默认未登录动作会跳转到setting里面的login_url,但是前后端</span></span><br><span class="line"><span class="string">分离应用中,应该将跳转动作交给前端去处理'''</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">myAuthenticated</span><span class="params">(method)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> ENV.lower() == <span class="string">"dev"</span>:</span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">warpper</span><span class="params">(*args, **kwargs)</span>:</span></span><br><span class="line"> ret = method(*args, **kwargs)</span><br><span class="line"> <span class="keyword">return</span> ret</span><br><span class="line"> <span class="keyword">return</span> warpper</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"><span class="meta"> @functools.wraps(method)</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span><span class="params">(self, *args, **kwargs)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> self.current_user:</span><br><span class="line"> <span class="keyword">raise</span> HTTPError(<span class="number">403</span>)</span><br><span class="line"> <span class="keyword">return</span> method(self, *args, **kwargs)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line"><span class="comment">#使用如下</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SaveModuleHandler</span><span class="params">(BaseHandler)</span>:</span></span><br><span class="line"><span class="meta"> @myAuthenticated</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self)</span>:</span></span><br><span class="line">...</span><br></pre></td></tr></table></figure></p><p>3. 开发环境设置响应头解决同源限制:<br><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BaseHandler</span><span class="params">(tornado.web.RequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">set_default_headers</span><span class="params">(self)</span>:</span></span><br><span class="line"><span class="keyword">if</span> ENV.lower() == <span class="string">"dev"</span>:</span><br><span class="line"> self.set_header(<span class="string">"Access-Control-Allow-Origin"</span>, <span class="string">"*"</span>)</span><br><span class="line"> self.set_header(<span class="string">"Access-Control-Allow-Headers"</span>, <span class="string">"Origin, X-Requested-With, Content-Type, Accept"</span>)</span><br><span class="line"> self.set_header(<span class="string">'Access-Control-Allow-Methods'</span>, <span class="string">'POST,GET,PUT,DELETE,OPTIONS'</span>)</span><br></pre></td></tr></table></figure></p><p>4. 开发环境禁用xsrf校验,并设置xsrf白名单:<br><figure class="highlight python"><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BaseHandler</span><span class="params">(tornado.web.RequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line"> _xsrf_WhiteList = [<span class="string">"/uri1"</span>,<span class="string">"/uri2"</span>,<span class="string">"/uri3"</span>]</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">check_xsrf_cookie</span><span class="params">(self)</span>:</span></span><br><span class="line"><span class="keyword">if</span> ENV.lower() == <span class="string">"dev"</span>:</span><br><span class="line"><span class="keyword">return</span></span><br><span class="line"> reqPath = self.request.path</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> any([reqPath <span class="keyword">in</span> url <span class="keyword">for</span> url <span class="keyword">in</span> self._xsrf_WhiteList]):</span><br><span class="line"> token = (self.get_argument(<span class="string">"_xsrf"</span>, <span class="keyword">None</span>) <span class="keyword">or</span></span><br><span class="line"> self.request.headers.get(<span class="string">"X-Xsrftoken"</span>) <span class="keyword">or</span></span><br><span class="line"> self.request.headers.get(<span class="string">"X-Csrftoken"</span>))</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> token:</span><br><span class="line"></span><br><span class="line"> <span class="keyword">raise</span> HTTPError(<span class="number">403</span>, <span class="string">"服务器内部错误1"</span>)</span><br><span class="line"> _, token, _ = self._decode_xsrf_token(token)</span><br><span class="line"> _, expected_token, _ = self._get_raw_xsrf_token()</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> token:</span><br><span class="line"> <span class="keyword">raise</span> HTTPError(<span class="number">403</span>, <span class="string">"服务器内部错误2"</span>)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> _time_independent_equals(utf8(token), utf8(expected_token)):</span><br><span class="line"> <span class="keyword">raise</span> HTTPError(<span class="number">403</span>, <span class="string">"服务器内部错误3"</span>)</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span></span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<p>在开发WEB前后端分离的应用时,在开发环境中,前后端应用访问的主机地址(ip:port)不同;但在生产环境,一般都会使用nginx代理前后端应用使得前后端的访问地址为同一个。所以,前后端应用在不同环境中的某些行为也不相同。比如,在开发环境中,由于浏览器的同源策略,导致前端的
</summary>
<category term="WEB开发" scheme="http://yoursite.com/categories/WEB%E5%BC%80%E5%8F%91/"/>
<category term="前后端分离" scheme="http://yoursite.com/tags/%E5%89%8D%E5%90%8E%E7%AB%AF%E5%88%86%E7%A6%BB/"/>
<category term="vuejs" scheme="http://yoursite.com/tags/vuejs/"/>
<category term="tornado" scheme="http://yoursite.com/tags/tornado/"/>
<category term="Debug" scheme="http://yoursite.com/tags/Debug/"/>
</entry>
<entry>
<title>快速定位前端加密方法</title>
<link href="http://yoursite.com/2019/10/08/%E5%BF%AB%E9%80%9F%E5%AE%9A%E4%BD%8D%E5%89%8D%E7%AB%AF%E5%8A%A0%E5%AF%86%E6%96%B9%E6%B3%95/"/>
<id>http://yoursite.com/2019/10/08/快速定位前端加密方法/</id>
<published>2019-10-08T02:00:34.000Z</published>
<updated>2019-10-08T02:53:11.569Z</updated>
<content type="html"><![CDATA[<p><em style="font-size: 0.86em;">来源:<a href="http://gv7.me/articles/2018/fast-locate-the-front-end-encryption-method/" target="_blank" rel="noopener">http://gv7.me/articles/2018/fast-locate-the-front-end-encryption-method/</a><br>作者:c0ny1</em></p><p>相信用过我jsEncrypter这个插件的朋友,都会碰到一个问题。 <strong>那就是一些大型网站前端太复杂,以至于无法定位到前端数据加密函数所在的位置</strong>。无法定位到加密方法所在,自然就无法编写jsEncrypter的phantomJS脚本了。k哥在今晚给了我很多灵感,让我对这个问题有一个完美的解决方案。以至于现在已是12号的凌晨3点,我仍不舍得搁浅内心零散的想法。窗外稍许的车辆略过的轰鸣,在夜深人静时显得格外刺耳。不过还好,没破坏我静静码字感觉。下面让我慢慢将这简单弱智有点零散,但细细思考,却有点意思的想法,串成一个流程。</p><h3 id="0x01-onClick定位法"><a href="#0x01-onClick定位法" class="headerlink" title="# 0x01 onClick定位法"></a><span style="color:#2bbc8a">#</span> 0x01 onClick定位法</h3><p>有时候在触发提交表单的标签中会存在一个onClik属性,该属性的值正好是一个js函数。而这个函数往往就是我们要找的数据加密函数。我们只需要找到它定义的地方即可。<br><img src="/upload/LocateFrontEncryptFunc_LocationByonClick.png" alt="imgName"><center>图1-通过onClick属性定位</center><br>找到了加密数据的方法名之后,我们就可以去找一下该方法在那个js文件中定义,即可定位到位置。<br><img src="/upload/LocateFrontEncryptFunc_FindFunctionFromOnClick.png" alt="imgName"><center>图2-通过onClick定位到的方法</center></p><h3 id="0x02-Event-Listeners定位法"><a href="#0x02-Event-Listeners定位法" class="headerlink" title="# 0x02 Event Listeners定位法"></a><span style="color:#2bbc8a">#</span> 0x02 Event Listeners定位法</h3><p>这个方法非常好,也是我觉得最好的方法。F12打开开发者工具,然后使用选择箭头选择目标标签,最后打开开发者工具Event Listeners面板。就能显示该标签对应的额事件了。我们关注的当然是click事件了。<br><img src="/upload/LocateFrontEncryptFunc_LocationByEventListeners.png" alt="imgName"><center>图3-通过Event Listeners定位</center><br>由此我们就知道,我们的数据加密方法在<code>uni_loginv4_tangram_dde753f.js</code>文件的32行。点击该链接就能直接调转到代码处。<br><img src="/upload/LocateFrontEncryptFunc_FindFunctionFromEventListeners.png" alt="imgName"><center>图4-通过Event Listeners定位的代码</center><br>这个方法虽然非常好,但是有一个天坑需要注意!有时候标签是有绑定方法的,但看到Event Listeners面板却是空的。我猜是因为浏览器它没有加载完全所有的数据,导致无法分析出各个元素绑定的方法。这时我们可以进行将登录整个流程走一遍,多次刷新页面,甚至可以ctrl+s将网页保存到本地等操作,总之只为一个目的: 间接告诉浏览器赶紧将一些网页资源保存下来,以供Event Listeners分析出click事件对应的方法。目前发现这样勉强能解决。</p><p>这里插一句题外话:有一个和Event Listeners有关的辅助插件Visual Event,大家可以去体验一下。不过个人觉得不是特别好!</p><h3 id="0x03-搜索定位法"><a href="#0x03-搜索定位法" class="headerlink" title="# 0x03 搜索定位法"></a><span style="color:#2bbc8a">#</span> 0x03 搜索定位法</h3><p>如果遇到的情况很糟糕,页面没有指定onClick方法,Event Listeners怎么操作都是空白一片,Visual Event也是半死不活的时候。这是我们就只能自己动手,丰衣足食了。当然我承认这种情况基本不可能发生。然而谁还没有个万一呢?</p><p>先将页面ctrl+s,保存起来。然后使用notepad++搜索保存目录下所有内容。这时我们就要考虑寻找搜索关键字了。搜索操作过程虽然有点繁琐,但很简单。这里我挑比较有意思的选择搜索关键字的思考跟大家分享一下。</p><ol><li><p>从源头搜,什么是我们的源头搜呢?我们触发前端数据加密,然后进行传输的整个过程皆因为点击了一个标签造成。所以我们就可以通过这个标签的id名,class名或者标签名作为关键字去搜索,就能定位到开始进行加密处理的位置。最后根据起始位置,一步一步跟进就能找到我们的加密方法。</p></li><li><p>从终点搜,什么是我们的终点呢?当然是我们的最终发送数据包这一步了。我们可以用burp进行抓包,然后分析数据包的特点,提取关键字来定位。比如我们可以拿数据包提交的路径,可以拿数据包的参数等等作为关键字。定位到加密流程的最后一步,最后一步一步回溯找到加密方法。</p></li></ol><p>例如:我打算从源头开始搜,查看到源码中淘宝的登录按钮标签id值为<code>J_SubmitStatic</code>,于是我以<code>#J_SubmitStatic</code>作为关键字开始定位。<br><img src="/upload/LocateFrontEncryptFunc_LocationByKeyword.png" alt="imgName"><center>图5-通过关键定位</center></p><h3 id="0x04-调试确认"><a href="#0x04-调试确认" class="headerlink" title="# 0x04 调试确认"></a><span style="color:#2bbc8a">#</span> 0x04 调试确认</h3><p>在使用了以上三个方法加辅助插件,基本可以保证能定位到99%网站前端密码的处理函数了。但我们仍然需要通过调式来确定我们定位到的地方就是数据加密方法。首选我们在定位的方法中打一个断点,然后在表单输入账号密码,最后点击提交。就可以进入调试模式了。进入调式模式,我们可以单步执行,梳理加密处理的每一步。方便我们更好的编写jsEncrypter插件的phantomJs脚本。<br><img src="/upload/LocateFrontEncryptFunc_debug.png" alt="imgName"><center>图6-调试确认</center></p><h3 id="0x05-最后的话"><a href="#0x05-最后的话" class="headerlink" title="# 0x05 最后的话"></a><span style="color:#2bbc8a">#</span> 0x05 最后的话</h3><p>我使用了以上流程,先后定位到了百度,淘宝,腾讯和京东的前端页面数据加密方法。证明了我们的流程大体还是很实用的。各位同学可以按照上面的方法去测试一下,看看自己能否快速定位到数据加密方法?当然你有更加快速的方法,欢迎留言,让我们的这个快速定位前端加密方法的流程更加完美!已是凌晨4点,明天还有工作。祝每个还在深夜码字写代码的灵魂晚安!</p>]]></content>
<summary type="html">
<p><em style="font-size: 0.86em;">来源:<a href="http://gv7.me/articles/2018/fast-locate-the-front-end-encryption-method/" target="_blank" rel=
</summary>
<category term="前端" scheme="http://yoursite.com/categories/%E5%89%8D%E7%AB%AF/"/>
<category term="加解密" scheme="http://yoursite.com/categories/%E5%89%8D%E7%AB%AF/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
<category term="前端" scheme="http://yoursite.com/tags/%E5%89%8D%E7%AB%AF/"/>
<category term="加解密" scheme="http://yoursite.com/tags/%E5%8A%A0%E8%A7%A3%E5%AF%86/"/>
<category term="爬虫" scheme="http://yoursite.com/tags/%E7%88%AC%E8%99%AB/"/>
</entry>
<entry>
<title>利用EBNF语法解析实现简单的数学表达式计算</title>
<link href="http://yoursite.com/2019/08/21/%E5%88%A9%E7%94%A8EBNF%E8%AF%AD%E6%B3%95%E8%A7%A3%E6%9E%90%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84%E6%95%B0%E5%AD%A6%E8%A1%A8%E8%BE%BE%E5%BC%8F%E8%AE%A1%E7%AE%97/"/>
<id>http://yoursite.com/2019/08/21/利用EBNF语法解析实现简单的数学表达式计算/</id>
<published>2019-08-21T08:06:51.000Z</published>
<updated>2019-08-21T10:01:21.151Z</updated>
<content type="html"><![CDATA[<p>  当你想根据一组语法规则解析文本并执行命令时,我们可以先以BNF或者EBNF形式指定一个标准语法,再根据正则规则将文本分解为一组令牌流,然后根据(E)BNF语法依次处理令牌流。</p><h3 id="1-EBNF简介"><a href="#1-EBNF简介" class="headerlink" title="1. EBNF简介"></a>1. EBNF简介</h3><p>  EBNF(Extended Backus–Naur Form,扩展的巴克斯范式)是一种用于描述计算机编程语言等正式语言的与上下文无关语法的元语法(metasyntax)符号表示法。简而言之,它是一种描述语言的语言。</p><p>  EBNF一条规则基本书写规范如下:</p><pre><code>symbol ::= alternative1 | alternative2 ...</code></pre><p>出现在规则左边的符号称为非终端,终端用双引号或单引号包裹起来。<br>BNF中有一个特殊符号“@”,表示符号可以去掉。如果用@替换符号,只需要将符号去掉。</p><table><thead><tr><th style="text-align:center">记号</th><th style="text-align:center">意义</th></tr></thead><tbody><tr><td style="text-align:center">[…]</td><td style="text-align:center">可选</td></tr><tr><td style="text-align:center">{…}*</td><td style="text-align:center">重复,*表示重复0次或多次,和正则中一致。(?出现0次或1次,+出现至少1次)</td></tr><tr><td style="text-align:center">(…)</td><td style="text-align:center">分组</td></tr><tr><td style="text-align:center">|</td><td style="text-align:center">并列选项,只能选一个</td></tr><tr><td style="text-align:center">“…”</td><td style="text-align:center">终端字符串</td></tr><tr><td style="text-align:center">‘…’</td><td style="text-align:center">终端字符串</td></tr></tbody></table><p>例如,用EBNF定义实数:</p><pre><code>S := '-' FN | FN FN := DL FP FP := @ | '.' DL DL := D DR DR := D DR | @ D := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' </code></pre><h3 id="2-解析文本为令牌流"><a href="#2-解析文本为令牌流" class="headerlink" title="2. 解析文本为令牌流"></a>2. 解析文本为令牌流</h3><p>2.1 先将文本分割成最小处理单元,同一类单元称为同一个Token,为所有Token定义匹配模式,并使用<code>?P<TOKENNAME></code>指定模式名称。<br>例如,对于<code>'foo = 23 + 43 * 10'</code>定义匹配模式如下: </p><figure class="highlight python"><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"><span class="keyword">import</span> re</span><br><span class="line">NAME = <span class="string">r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)'</span></span><br><span class="line">NUM = <span class="string">r'(?P<NUM>\d+)'</span></span><br><span class="line">PLUS = <span class="string">r'(?P<PLUS>\+)'</span></span><br><span class="line">TIMES = <span class="string">r'(?P<TIMES>\*)'</span></span><br><span class="line">EQ = <span class="string">r'(?P<EQ>=)'</span></span><br><span class="line">WS = <span class="string">r'(?P<WS>\s+)'</span></span><br><span class="line">master_pat = re.compile(<span class="string">'|'</span>.join([NAME, NUM, PLUS, TIMES, EQ, WS]))</span><br></pre></td></tr></table></figure><p>2.2 根据上面生成的模式规则,将文本扫描成令牌流</p><figure class="highlight python"><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"><span class="keyword">from</span> collections <span class="keyword">import</span> namedtuple</span><br><span class="line">Token = namedtuple(<span class="string">"Token"</span>,[<span class="string">"type"</span>,<span class="string">"value"</span>])</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate_tokens</span><span class="params">(text)</span>:</span></span><br><span class="line"> scanner = master_pat.scanner(text)</span><br><span class="line"> <span class="keyword">for</span> m <span class="keyword">in</span> iter(scanner.match,<span class="keyword">None</span>):</span><br><span class="line"> <span class="keyword">if</span> m.lastgroup != <span class="string">'WS'</span>:</span><br><span class="line"> <span class="keyword">yield</span> Token(m.lastgroup,m.group())</span><br></pre></td></tr></table></figure><p>说明:</p><ul><li><code>re.Pattern</code>对象的<code>scanner()</code>方法返回<code><class '_sre.SRE_Scanner'</code>>类型对象</li><li><code><class '_sre.SRE_Scanner'</code>>类型对象拥有match()方法,该方法类似于可迭代对象中的<code>__next__()</code>方法,每次调用根据匹配顺序依次返回一个匹配到的<code>re.Match</code>对象。直到文本内容结束或者碰到文本中存在不可匹配的内容时,扫描结束,返回<code>None</code></li><li><code>re.Match</code>对象拥有属性<code>lastgroup</code>保存分组组名(即<code>?P<TOKENNAME>...</code>中的TOKENNAME);拥有方法 <code>group()</code> 返回匹配到的文本内容</li><li><code>iter(object[, sentinel])</code>如果没有传入第二个参数,则第一个参数需传入一个序列对象,例如list,str等等; 如果传入第二个参数,则第一个参数需传入可调用对象或方法,且其支持多次调用,直到调用返回值等于sentinel时,调用结束</li></ul><h3 id="3-源码如下:"><a href="#3-源码如下:" class="headerlink" title="3. 源码如下:"></a>3. 源码如下:</h3><figure class="highlight python"><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><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> re</span><br><span class="line"><span class="keyword">from</span> collections <span class="keyword">import</span> namedtuple</span><br><span class="line"></span><br><span class="line">DECIMAL = <span class="string">r'(?P<DECIMAL>\d+(\.\d+)?)'</span></span><br><span class="line">PLUS = <span class="string">r'(?P<PLUS>\+)'</span></span><br><span class="line">MINUS = <span class="string">r'(?P<MINUS>-)'</span></span><br><span class="line">MULTI = <span class="string">r'(?P<MULTI>\*)'</span></span><br><span class="line">DIVIDE = <span class="string">r'(?P<DIVIDE>/)'</span></span><br><span class="line">LPAREN = <span class="string">r'(?P<LPAREN>\()'</span></span><br><span class="line">RPAREN = <span class="string">r'(?P<RPAREN>\))'</span></span><br><span class="line">WS = <span class="string">r'(?P<WS>\s+)'</span></span><br><span class="line"></span><br><span class="line">master_pat = re.compile(<span class="string">'|'</span>.join([DECIMAL,PLUS,MINUS,MULTI,DIVIDE,LPAREN,RPAREN,WS]))</span><br><span class="line"></span><br><span class="line">Token = namedtuple(<span class="string">"Token"</span>,[<span class="string">"type"</span>,<span class="string">"value"</span>])</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">generate_tokens</span><span class="params">(text)</span>:</span></span><br><span class="line"> scanner = master_pat.scanner(text)</span><br><span class="line"> <span class="keyword">for</span> m <span class="keyword">in</span> iter(scanner.match,<span class="keyword">None</span>):</span><br><span class="line"> <span class="keyword">if</span> m.lastgroup != <span class="string">'WS'</span>:</span><br><span class="line"> <span class="keyword">yield</span> Token(m.lastgroup,m.group())</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">mathExpressionEvaluator</span>:</span></span><br><span class="line"> <span class="string">'''</span></span><br><span class="line"><span class="string"> 支持实数范围内的加减乘数表达式计算。</span></span><br><span class="line"><span class="string"> parse 方法接受表达式字符串,并返回计算结果.</span></span><br><span class="line"><span class="string"> _accept 方法判断是否存在下一个token,并返回True或者False</span></span><br><span class="line"><span class="string"> '''</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">parse</span><span class="params">(self,text)</span>:</span></span><br><span class="line"> self.tokens = generate_tokens(text)</span><br><span class="line"> self.tok = <span class="keyword">None</span></span><br><span class="line"> self.nextTok = <span class="keyword">None</span></span><br><span class="line"> self._getTok()</span><br><span class="line"> <span class="keyword">return</span> self.expr()</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_getTok</span><span class="params">(self)</span>:</span></span><br><span class="line"> self.tok,self.nextTok = self.nextTok,next(self.tokens,<span class="keyword">None</span>)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_accept</span><span class="params">(self,toktype)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> self.nextTok <span class="keyword">and</span> self.nextTok.type==toktype:</span><br><span class="line"> self._getTok()</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">True</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">False</span></span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">_expected</span><span class="params">(self,toktype)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> self._accept(toktype):</span><br><span class="line"> <span class="keyword">raise</span> SyntaxError(<span class="string">'Expected '</span>+ toktype)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="string">"""</span></span><br><span class="line"><span class="string"> 加减乘除表达式的 EBNF 语法规则:</span></span><br><span class="line"><span class="string"> expr ::= term { (+|-)term }*</span></span><br><span class="line"><span class="string"> term ::= factor { (*|/)factor }*</span></span><br><span class="line"><span class="string"> factor ::= DECIMAL | (expr) </span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">expr</span><span class="params">(self)</span>:</span></span><br><span class="line"> expr_val = self.term()</span><br><span class="line"> <span class="keyword">while</span> self._accept(<span class="string">"PLUS"</span>) <span class="keyword">or</span> self._accept(<span class="string">"MINUS"</span>):</span><br><span class="line"> op = self.tok.type</span><br><span class="line"> right = self.term()</span><br><span class="line"> <span class="keyword">if</span> op==<span class="string">"PLUS"</span>:</span><br><span class="line"> expr_val += right</span><br><span class="line"> <span class="keyword">elif</span> op == <span class="string">"MINUS"</span>:</span><br><span class="line"> expr_val -= right</span><br><span class="line"> <span class="keyword">return</span> expr_val</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">term</span><span class="params">(self)</span>:</span></span><br><span class="line"> term_val = self.factor()</span><br><span class="line"> <span class="keyword">while</span> self._accept(<span class="string">"MULTI"</span>) <span class="keyword">or</span> self._accept(<span class="string">"DIVIDE"</span>):</span><br><span class="line"> op = self.tok.type</span><br><span class="line"> right = self.factor()</span><br><span class="line"> <span class="keyword">if</span> op == <span class="string">"MULTI"</span>:</span><br><span class="line"> term_val *= right</span><br><span class="line"> <span class="keyword">elif</span> op == <span class="string">"DIVIDE"</span>:</span><br><span class="line"> term_val /= right</span><br><span class="line"> <span class="keyword">return</span> term_val</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">factor</span><span class="params">(self)</span>:</span></span><br><span class="line"> <span class="keyword">if</span> self._accept(<span class="string">"DECIMAL"</span>):</span><br><span class="line"> <span class="keyword">return</span> float(self.tok.value)</span><br><span class="line"> <span class="keyword">elif</span> self._accept(<span class="string">"LPAREN"</span>):</span><br><span class="line"> factor_val = self.expr()</span><br><span class="line"> self._expected(<span class="string">"RPAREN"</span>)</span><br><span class="line"> <span class="keyword">return</span> factor_val</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">raise</span> SyntaxError(<span class="string">'Expected DECIMAL or LPAREN'</span>)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">descent_parser</span><span class="params">()</span>:</span></span><br><span class="line"> e = mathExpressionEvaluator()</span><br><span class="line"> expressionList = [<span class="string">'2'</span>,<span class="string">'2 + 3.5'</span>,<span class="string">'6 + 1.5 * (3 - 1)'</span>,<span class="string">'10 / (5 - 3) * (3 + 5)'</span>]</span><br><span class="line"> <span class="keyword">for</span> expression <span class="keyword">in</span> expressionList:</span><br><span class="line"> print(<span class="string">'{} = {}'</span>.format(expression,e.parse(expression)))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> descent_parser()</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>&emsp;&emsp;当你想根据一组语法规则解析文本并执行命令时,我们可以先以BNF或者EBNF形式指定一个标准语法,再根据正则规则将文本分解为一组令牌流,然后根据(E)BNF语法依次处理令牌流。</p>
<h3 id="1-EBNF简介"><a href="#1-EBN
</summary>
<category term="EBNF范式" scheme="http://yoursite.com/categories/EBNF%E8%8C%83%E5%BC%8F/"/>
<category term="Python" scheme="http://yoursite.com/categories/EBNF%E8%8C%83%E5%BC%8F/Python/"/>
<category term="文本解析" scheme="http://yoursite.com/tags/%E6%96%87%E6%9C%AC%E8%A7%A3%E6%9E%90/"/>
<category term="EBNF范式" scheme="http://yoursite.com/tags/EBNF%E8%8C%83%E5%BC%8F/"/>
</entry>
<entry>
<title>python流量嗅探</title>
<link href="http://yoursite.com/2018/09/30/python%E6%B5%81%E9%87%8F%E5%97%85%E6%8E%A2/"/>
<id>http://yoursite.com/2018/09/30/python流量嗅探/</id>
<published>2018-09-30T00:57:09.000Z</published>
<updated>2018-09-30T06:02:18.416Z</updated>
<content type="html"><![CDATA[<p>有时候,我们需要对客户端流量进行分析,了解未知的协议,但在一些严格企业级环境中,可能会遇到wireshark无法使用的情况,这时候就需要自己去编写一个本地工具以获取指定网卡流量信息。 </p><figure class="highlight python"><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><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#coding=utf-8</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="string">指定本地网卡IP和远程主机地址,监控TCP流量的接受发送</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="keyword">import</span> socket</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> struct</span><br><span class="line"><span class="keyword">from</span> ctypes <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">hexdump</span><span class="params">(src, length=<span class="number">16</span>)</span>:</span></span><br><span class="line"> result = []</span><br><span class="line"> digits = <span class="number">4</span> <span class="keyword">if</span> isinstance(src, unicode) <span class="keyword">else</span> <span class="number">2</span></span><br><span class="line"> <span class="keyword">for</span> i <span class="keyword">in</span> xrange(<span class="number">0</span>, len(src), length):</span><br><span class="line"> s = src[i:i + length]</span><br><span class="line"> hexa = <span class="string">b' '</span>.join([<span class="string">"%0*X"</span> % (digits, ord(x)) <span class="keyword">for</span> x <span class="keyword">in</span> s])</span><br><span class="line"> text = <span class="string">b''</span>.join([x <span class="keyword">if</span> <span class="number">0x20</span> <= ord(x) < <span class="number">0x7F</span> <span class="keyword">else</span> <span class="string">"<%s>"</span>%x.encode(<span class="string">"string-escape"</span>) <span class="keyword">for</span> x <span class="keyword">in</span> s]) <span class="comment">#非ascii可显示字符用.代替,将x.encode("string-escape")替换成b'.'</span></span><br><span class="line"> result.append(<span class="string">b"%04X %-*s %s"</span> % (i, length * (digits + <span class="number">1</span>), hexa, text))</span><br><span class="line"> print(<span class="string">b'\n'</span>.join(result))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">IP</span><span class="params">(Structure)</span>:</span></span><br><span class="line"> _fields_=[</span><br><span class="line"> (<span class="string">"ihl"</span>, c_ubyte,<span class="number">4</span>), <span class="comment">#第三个值代表4bit,0.5byte , 不足一个字节是前后相反</span></span><br><span class="line"> (<span class="string">"version"</span>,c_ubyte,<span class="number">4</span>), <span class="comment">#4bit,0.5byte</span></span><br><span class="line"> (<span class="string">"tos"</span>,c_ubyte), <span class="comment">#8bit,1byte</span></span><br><span class="line"> (<span class="string">"len"</span>,c_ushort), <span class="comment">#16bit,2byte</span></span><br><span class="line"> (<span class="string">"id"</span>,c_ushort), <span class="comment">#16bit,2byte</span></span><br><span class="line"> (<span class="string">"offset"</span>,c_ushort), <span class="comment">#16bit,2byte</span></span><br><span class="line"> (<span class="string">"ttl"</span>,c_ubyte), <span class="comment">#8bit,1byte</span></span><br><span class="line"> (<span class="string">"protocol_num"</span>,c_ubyte), <span class="comment">#8bit,1byte</span></span><br><span class="line"> (<span class="string">"sum"</span>,c_ushort), <span class="comment">#16bit,2byte</span></span><br><span class="line"> (<span class="string">"src"</span>,c_ulong), <span class="comment">#32bit,4byte</span></span><br><span class="line"> (<span class="string">"dst"</span>,c_ulong) <span class="comment">#32bit,4byte</span></span><br><span class="line"> ]</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__new__</span><span class="params">(self,socket_buffer=None)</span>:</span></span><br><span class="line"> <span class="keyword">return</span> self.from_buffer_copy(socket_buffer)</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">__init__</span><span class="params">(self,socket_buffer=None)</span>:</span></span><br><span class="line"> self.protocol_map = {<span class="number">1</span>:<span class="string">"ICMP"</span>,<span class="number">6</span>:<span class="string">"TCP"</span>,<span class="number">17</span>:<span class="string">"UDP"</span>}</span><br><span class="line"></span><br><span class="line"> self.src_address = socket.inet_ntoa(struct.pack(<span class="string">"<L"</span>,self.src))</span><br><span class="line"> self.dst_address = socket.inet_ntoa(struct.pack(<span class="string">"<L"</span>,self.dst))</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> self.protocol = self.protocol_map[self.protocol_num]</span><br><span class="line"> <span class="keyword">except</span>:</span><br><span class="line"> self.protocol = str(self.protocol_num)</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">main</span><span class="params">(localhost,remotehost,remoteport)</span>:</span></span><br><span class="line"> <span class="comment"># 创建原始套接字,绑定在公开接口上</span></span><br><span class="line"> <span class="keyword">if</span> os.name == <span class="string">"nt"</span>:</span><br><span class="line"> socket_protocol = socket.IPPROTO_IP</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> socket_protocol = socket.IPPROTO_ICMP</span><br><span class="line"></span><br><span class="line"> sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol)</span><br><span class="line"></span><br><span class="line"> sniffer.bind((localhost, <span class="number">0</span>))</span><br><span class="line"> <span class="comment"># 设置在捕获的数据包中包含IP头</span></span><br><span class="line"> sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, <span class="number">1</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># 在WIN平台上,需要设置IOCTL以启用混杂模式</span></span><br><span class="line"> <span class="keyword">if</span> os.name == <span class="string">"nt"</span>:</span><br><span class="line"> sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">try</span>:</span><br><span class="line"> <span class="keyword">while</span> <span class="keyword">True</span>:</span><br><span class="line"> raw_buffer = sniffer.recvfrom(<span class="number">65565</span>)[<span class="number">0</span>]</span><br><span class="line"> ip_header = IP(raw_buffer[<span class="number">0</span>:<span class="number">20</span>])</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ip_header.protocol==<span class="string">"TCP"</span> <span class="keyword">and</span> ip_header.dst_address == remotehost:</span><br><span class="line"> offset = ip_header.ihl * <span class="number">4</span></span><br><span class="line"> ipContent = raw_buffer[offset:]</span><br><span class="line"> tcp_header = ipContent[<span class="number">0</span>:<span class="number">20</span>]</span><br><span class="line"> sourcePort = int(tcp_header[<span class="number">0</span>:<span class="number">2</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> destPort = int(tcp_header[<span class="number">2</span>:<span class="number">4</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> <span class="keyword">print</span> <span class="string">"Protocol:%s %s:%s -> %s:%s"</span> % (ip_header.protocol, ip_header.src_address,sourcePort, ip_header.dst_address,destPort)</span><br><span class="line"> <span class="keyword">if</span> destPort == remoteport:</span><br><span class="line"> sequenceNumber = int(tcp_header[<span class="number">4</span>:<span class="number">8</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> acknowledgeNumber = int(tcp_header[<span class="number">8</span>:<span class="number">12</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>) <span class="comment"># acknowledge number 表示的是,接收方期待接收的下一个包起始字节的标号</span></span><br><span class="line"> tcpContent = ipContent[<span class="number">20</span>:]</span><br><span class="line"> <span class="keyword">print</span> <span class="string">"send %d bytes to remote.SYN=%s ACK=%s"</span>%(len(tcpContent),sequenceNumber,acknowledgeNumber)</span><br><span class="line"> hexdump(tcpContent)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> ip_header.protocol==<span class="string">"TCP"</span> <span class="keyword">and</span> ip_header.src_address == remotehost:</span><br><span class="line"> offset = ip_header.ihl * <span class="number">4</span></span><br><span class="line"> ipContent = raw_buffer[offset:]</span><br><span class="line"> tcp_header = ipContent[<span class="number">0</span>:<span class="number">20</span>]</span><br><span class="line"> sourcePort = int(tcp_header[<span class="number">0</span>:<span class="number">2</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> destPort = int(tcp_header[<span class="number">2</span>:<span class="number">4</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> <span class="keyword">print</span> <span class="string">"Protocol:%s %s:%s -> %s:%s"</span> % (ip_header.protocol, ip_header.src_address,sourcePort, ip_header.dst_address,destPort)</span><br><span class="line"> <span class="keyword">if</span> sourcePort == remoteport:</span><br><span class="line"> sequenceNumber = int(tcp_header[<span class="number">4</span>:<span class="number">8</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>)</span><br><span class="line"> acknowledgeNumber = int(tcp_header[<span class="number">8</span>:<span class="number">12</span>].encode(<span class="string">"hex"</span>),<span class="number">16</span>) <span class="comment"># acknowledge number 表示的是,接收方期待接收的下一个包起始字节的标号</span></span><br><span class="line"> tcpContent = ipContent[<span class="number">20</span>:]</span><br><span class="line"> <span class="keyword">print</span> <span class="string">"recieved %d bytes from remote.SYN=%s ACK=%s"</span>%(len(tcpContent),sequenceNumber,acknowledgeNumber)</span><br><span class="line"> hexdump(tcpContent)</span><br><span class="line"></span><br><span class="line"> <span class="keyword">except</span> KeyboardInterrupt:</span><br><span class="line"> <span class="comment"># 关闭混杂模式</span></span><br><span class="line"> <span class="keyword">if</span> os.name == <span class="string">"nt"</span>:</span><br><span class="line"> sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> main(<span class="string">"10.126.1.209"</span>,<span class="string">"172.30.1.71"</span>,<span class="number">80</span>)</span><br></pre></td></tr></table></figure><p>TCP3次握手: </p><pre><code>Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80send 12 bytes to remote.SYN=3097868532 ACK=00000 02 04 05 B4 01 03 03 08 01 01 04 02 <\x02><\x04><\x05><\xb4><\x01><\x03><\x03><\x08><\x01><\x01><\x04><\x02>Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80send 0 bytes to remote.SYN=3097868533 ACK=1855514852Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808recieved 12 bytes from remote.SYN=1855514851 ACK=30978685330000 02 04 05 B4 01 03 03 05 04 02 00 00 <\x02><\x04><\x05><\xb4><\x01><\x03><\x03><\x05><\x04><\x02><\x00><\x00></code></pre><p>TCP4次挥手: </p><pre><code>Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80send 0 bytes to remote.SYN=3097869053 ACK=1855517028Protocol:TCP 10.126.1.209:53808 -> 172.30.1.71:80send 0 bytes to remote.SYN=3097869054 ACK=1855517029Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808recieved 0 bytes from remote.SYN=1855517028 ACK=3097869054Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:53808recieved 0 bytes from remote.SYN=1855517028 ACK=3097869054</code></pre><p>HTTP请求: </p><pre><code>Protocol:TCP 10.126.1.209:49981 -> 172.30.1.71:80send 617 bytes to remote.SYN=3492030201 ACK=7558141280000 47 45 54 20 2F 20 48 54 54 50 2F 31 2E 31 0D 0A GET / HTTP/1.1<\r><\n>0010 48 6F 73 74 3A 20 31 37 32 2E 33 30 2E 31 2E 37 Host: 172.30.1.70020 31 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 6B 1<\r><\n>Connection: k0030 65 65 70 2D 61 6C 69 76 65 0D 0A 55 70 67 72 61 eep-alive<\r><\n>Upgra0040 64 65 2D 49 6E 73 65 63 75 72 65 2D 52 65 71 75 de-Insecure-Requ0050 65 73 74 73 3A 20 31 0D 0A 55 73 65 72 2D 41 67 ests: 1<\r><\n>User-Ag0060 65 6E 74 3A 20 4D 6F 7A 69 6C 6C 61 2F 35 2E 30 ent: Mozilla/5.00070 20 28 57 69 6E 64 6F 77 73 20 4E 54 20 36 2E 33 (Windows NT 6.30080 3B 20 57 69 6E 36 34 3B 20 78 36 34 29 20 41 70 ; Win64; x64) Ap0090 70 6C 65 57 65 62 4B 69 74 2F 35 33 37 2E 33 36 pleWebKit/537.3600A0 20 28 4B 48 54 4D 4C 2C 20 6C 69 6B 65 20 47 65 (KHTML, like Ge00B0 63 6B 6F 29 20 43 68 72 6F 6D 65 2F 36 38 2E 30 cko) Chrome/68.000C0 2E 33 34 34 30 2E 31 30 36 20 53 61 66 61 72 69 .3440.106 Safari00D0 2F 35 33 37 2E 33 36 0D 0A 41 63 63 65 70 74 3A /537.36<\r><\n>Accept:00E0 20 74 65 78 74 2F 68 74 6D 6C 2C 61 70 70 6C 69 text/html,appli00F0 63 61 74 69 6F 6E 2F 78 68 74 6D 6C 2B 78 6D 6C cation/xhtml+xml0100 2C 61 70 70 6C 69 63 61 74 69 6F 6E 2F 78 6D 6C ,application/xml0110 3B 71 3D 30 2E 39 2C 69 6D 61 67 65 2F 77 65 62 ;q=0.9,image/web0120 70 2C 69 6D 61 67 65 2F 61 70 6E 67 2C 2A 2F 2A p,image/apng,*/*0130 3B 71 3D 30 2E 38 0D 0A 41 63 63 65 70 74 2D 45 ;q=0.8<\r><\n>Accept-E0140 6E 63 6F 64 69 6E 67 3A 20 67 7A 69 70 2C 20 64 ncoding: gzip, d0150 65 66 6C 61 74 65 0D 0A 41 63 63 65 70 74 2D 4C eflate<\r><\n>Accept-L0160 61 6E 67 75 61 67 65 3A 20 7A 68 2D 43 4E 2C 7A anguage: zh-CN,z0170 68 3B 71 3D 30 2E 39 0D 0A 43 6F 6F 6B 69 65 3A h;q=0.9<\r><\n>Cookie:0180 20 63 73 72 66 74 6F 6B 65 6E 3D 63 50 33 30 30 csrftoken=cP3000190 64 30 75 47 42 4E 49 38 75 77 4C 32 45 31 31 4A d0uGBNI8uwL2E11J01A0 4C 66 37 42 57 6C 56 5A 32 72 57 44 4A 31 70 55 Lf7BWlVZ2rWDJ1pU01B0 31 52 6C 6C 7A 42 35 4F 6A 6C 77 33 37 44 68 53 1RllzB5Ojlw37DhS01C0 72 48 78 6A 36 31 46 45 6D 4C 44 3B 20 62 5F 74 rHxj61FEmLD; b_t01D0 5F 73 3D 22 32 7C 31 3A 30 7C 31 30 3A 31 35 33 _s="2|1:0|10:15301E0 38 32 30 33 39 34 34 7C 35 3A 62 5F 74 5F 73 7C 8203944|5:b_t_s|01F0 34 38 3A 4F 44 59 33 4F 57 4A 69 4E 54 55 74 5A 48:ODY3OWJiNTUtZ0200 47 55 7A 5A 69 30 30 4D 54 6B 79 4C 54 68 6D 4D GUzZi00MTkyLThmM0210 6D 55 74 5A 6A 52 6A 4E 54 41 7A 5A 44 5A 6D 59 mUtZjRjNTAzZDZmY0220 32 59 35 7C 62 66 30 30 65 30 36 61 32 34 33 65 2Y5|bf00e06a243e0230 61 61 38 39 38 38 30 30 32 34 30 32 35 66 39 30 aa89880024025f900240 37 33 34 38 30 37 32 32 33 62 30 35 62 62 31 31 734807223b05bb110250 39 38 32 39 38 31 35 36 66 39 32 31 63 35 64 63 98298156f921c5dc0260 34 65 61 31 22 0D 0A 0D 0A 4ea1"<\r><\n><\r><\n></code></pre><p>HTTP响应:</p><pre><code>Protocol:TCP 172.30.1.71:80 -> 10.126.1.209:49981recieved 238 bytes from remote.SYN=755814128 ACK=34920308180000 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D HTTP/1.1 200 OK<\r>0010 0A 53 65 72 76 65 72 3A 20 6E 67 69 6E 78 2F 31 <\n>Server: nginx/10020 2E 31 33 2E 39 0D 0A 44 61 74 65 3A 20 53 75 6E .13.9<\r><\n>Date: Sun0030 2C 20 33 30 20 53 65 70 20 32 30 31 38 20 30 30 , 30 Sep 2018 000040 3A 35 39 3A 33 37 20 47 4D 54 0D 0A 43 6F 6E 74 :59:37 GMT<\r><\n>Cont0050 65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 2F 68 ent-Type: text/h0060 74 6D 6C 0D 0A 43 6F 6E 74 65 6E 74 2D 4C 65 6E tml<\r><\n>Content-Len0070 67 74 68 3A 20 36 31 30 0D 0A 4C 61 73 74 2D 4D gth: 610<\r><\n>Last-M0080 6F 64 69 66 69 65 64 3A 20 54 68 75 2C 20 32 37 odified: Thu, 270090 20 53 65 70 20 32 30 31 38 20 30 34 3A 33 39 3A Sep 2018 04:39:00A0 31 38 20 47 4D 54 0D 0A 43 6F 6E 6E 65 63 74 69 18 GMT<\r><\n>Connecti00B0 6F 6E 3A 20 6B 65 65 70 2D 61 6C 69 76 65 0D 0A on: keep-alive<\r><\n>00C0 45 54 61 67 3A 20 22 35 62 61 63 35 65 66 36 2D ETag: "5bac5ef6-00D0 32 36 32 22 0D 0A 41 63 63 65 70 74 2D 52 61 6E 262"<\r><\n>Accept-Ran00E0 67 65 73 3A 20 62 79 74 65 73 0D 0A 0D 0A ges: bytes<\r><\n><\r><\n></code></pre><p>TCP为什么才用3次握手或者4次挥手呢?</p><p>我的理解是,3次握手保证了通讯双方以最少的次数确保信息传递的通路上,发送和接受都能保持通畅。 </p><p>通俗来讲就是,假如A、B通讯,对于A、B都能确保——<strong>1)能收到对方发送给自己的信息;2)自己发送的信息能被对方收到。</strong> </p><p>假如只握手两次的话,A发送消息给B,B返回确认消息。 对于A来讲,能够满足上面2个条件。但对于B来讲,只能满足第一个条件,而无法满足第二个条件。 </p><p>那为什么要4次挥手呢?<br>因为断开连接时,需要确保双方信息已经发送完毕。1)A发送结束信号给B;2)B告诉A:好的,我知道了;3)B继续发送数据完成,通知A:可以断开连接了;4)A收到B的通知后,断开连接。</p><p><strong>补充:</strong><br>ctypes 结构体位域<br>(位域名,c_ubyte,位域长度)<br>1.一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如: </p><pre><code>struct bs{ unsigned a:4 unsigned b:5 /*从下一单元开始存放*/ unsigned c:4}</code></pre><p>2.由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度。<br>3.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如: </p><pre><code>struct k{ int a:1 int :2 /*无位域名,该2位不能使用*/ int b:3 int c:2}; </code></pre><p>4.不够一个字节的位域,从后往前取bit </p><pre><code># -*-coding:utf-8 -*-from ctypes import *x = bytes("\x45\xA3\x35\x34\x56\x58\xA2\x31\x12\x89")print bin(int("45",16)) #0b01000101print bin(int("A3",16)) #0b10100011print bin(int("35",16)) #0b00110101class A(Structure): _fields_ = [ ("a",c_ubyte,3), #0b01000101,取最后3位 101 = 5 ("b",c_ubyte,3), #0b01000101,再往前取3位 000 = 0 ("c",c_ubyte,2), #0b01000101,取剩下的前2位 01 = 1 ("d",c_ubyte,4), #0b10100011,取4位,0011 = 3 ("e",c_ubyte,5) #0b00110101 前面剩余的位数不够,所以新使用一个字节,取后5位, 10101 = 21 ] def __new__(self,bytesBuffer): return self.from_buffer_copy(bytesBuffer) def __init__(self,bytesBuffer): passif __name__ == '__main__': print A(x).a #5 print A(x).b #0 print A(x).c #1 print A(x).d #3 print A(x).e #21</code></pre>]]></content>
<summary type="html">
<p>有时候,我们需要对客户端流量进行分析,了解未知的协议,但在一些严格企业级环境中,可能会遇到wireshark无法使用的情况,这时候就需要自己去编写一个本地工具以获取指定网卡流量信息。 </p>
<figure class="highlight python"><table
</summary>
<category term="python" scheme="http://yoursite.com/categories/python/"/>
<category term="网络安全" scheme="http://yoursite.com/categories/python/%E7%BD%91%E7%BB%9C%E5%AE%89%E5%85%A8/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="socket" scheme="http://yoursite.com/tags/socket/"/>
</entry>
<entry>
<title>tornado.web.RequestHandler对象详解</title>
<link href="http://yoursite.com/2018/08/02/tornado-web-RequestHandler%E5%AF%B9%E8%B1%A1%E8%AF%A6%E8%A7%A3/"/>
<id>http://yoursite.com/2018/08/02/tornado-web-RequestHandler对象详解/</id>
<published>2018-08-02T02:01:29.000Z</published>
<updated>2018-08-02T05:52:57.858Z</updated>
<content type="html"><![CDATA[<p>RequestHandler是tornado处理http请求的基类。对于一个http请求,使用此类获取请求的内容,并定制其响应内容。下面总结一下该类所包含的方法或变量。</p><h3 id="1、-self-request对象包含请求中的所有信息"><a href="#1、-self-request对象包含请求中的所有信息" class="headerlink" title="1、 self.request对象包含请求中的所有信息"></a>1、 self.request对象包含请求中的所有信息</h3><p>  使用dir(self.request)打印出来的内容有:[‘__class__‘, ‘__delattr__‘, ‘__dict__‘, ‘__doc__‘, ‘__format__‘, ‘__getattribute__‘, ‘__hash__‘, ‘__init__‘, ‘__module__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce<em>ex\</em>_‘, ‘__repr__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘__weakref__‘, ‘_finish_time’, ‘_parse_body’, ‘_start_time’, ‘arguments’, ‘body’, ‘body_arguments’, ‘connection’, ‘cookies’, ‘files’, ‘finish’, ‘full_url’, ‘get_ssl_certificate’, ‘headers’, ‘host’, ‘host_name’, ‘method’, ‘path’, ‘protocol’, ‘query’, ‘query_arguments’, ‘remote_ip’, ‘request_time’, ‘server_connection’, ‘supports_http_1_1’, ‘uri’, ‘version’, ‘write’] </p><p>实际请求如下:</p><pre><code>function post() { url = "http://10.126.1.209:8008/page?x=123&y=222&x=234" var htmlobj=$.ajax({url:url,async:false,data:"abc\nxyx",type:"POST",headers:{"user":"admin"}}); $("#response").html(htmlobj.responseText); }</code></pre><table><thead><tr><th style="text-align:left">名称</th><th style="text-align:left">说明</th><th style="text-align:left">示例</th></tr></thead><tbody><tr><td style="text-align:left">__class__</td><td style="text-align:left">具体的类名</td><td style="text-align:left"><class ‘tornado.httputil.HTTPServerRequest’></td></tr><tr><td style="text-align:left">__dict__</td><td style="text-align:left">self.request对象所包含的所有非继承属性</td><td style="text-align:left">{‘body’:’a=b&aa=bb’, <br>‘files’: {}, <br>‘protocol’: ‘http’,<br> ‘connection’: <tornado.http1connection.HTTP1Connection object at 0x0387D330>, <br>‘body_arguments’: {‘a’: [‘b’], ‘aa’: [‘bb’]}, <br>‘uri’: ‘/page’, <br>‘query_arguments’: {}, <br>‘_start_time’: 1533177225.76, <br>‘headers’: <tornado.httputil.HTTPHeaders object at 0x0387D470>,<br> ‘host’: ‘10.126.1.209:8008’,<br> ‘version’: ‘HTTP/1.1’,<br> ‘server_connection’: <tornado.http1connection.HTTP1ServerConnection object at 0x0387D2B0>, <br>‘host_name’: ‘10.126.1.209’, <br>‘_finish_time’: None, <br>‘query’: ‘’, <br>‘arguments’: {‘a’: [‘b’], ‘aa’: [‘bb’]}, <br>‘path’: ‘/page’, <br>‘method’: ‘POST’, <br>‘remote_ip’: ‘10.126.1.209’}</td></tr><tr><td style="text-align:left">_start_time</td><td style="text-align:left">请求开始时间</td><td style="text-align:left">1533177225.76</td></tr><tr><td style="text-align:left">arguments</td><td style="text-align:left">所有请求参数,包含url中的query部分,以及请求Body中的参数。(说明:query参数相同key可以保存多个value,body中的参数同名只会保存最后一个值)。<br>当请求Body为字符串的时候,将字符串作为arguments的key,value为空值</td><td style="text-align:left">a) {‘y’: [‘222’], ‘x’: [‘123’, ‘234’], ‘aa’: [‘bb’], ‘a’: [‘c’]}<br>b) 请求data变为”abc”时,{‘y’: [‘222’], ‘x’: [‘123’, ‘234’], ‘abc’: [‘’]}</td></tr><tr><td style="text-align:left">body</td><td style="text-align:left">字符串类型,请求Body内容,urlencode编码后的字符串</td><td style="text-align:left">a=c&aa=bb或者abc</td></tr><tr><td style="text-align:left">body_arguments</td><td style="text-align:left">同arguments,只包含body中的参数</td><td style="text-align:left">{‘a’: [‘c’], ‘aa’: [‘bb’]}或者{‘abc’: [‘’]}</td></tr><tr><td style="text-align:left">cookies</td><td style="text-align:left">获取请求cookies字符串内容</td><td style="text-align:left">_xsrf=2|b362be79|83193f43f7fb09b14cbd70ef9bfc1748|1532330344; user=”2|1:0|10:1533116808|4:user|8:YWRtaW4=|ac6a2f9f3df2c67e903490af706fe7881208abb30c4108b87779dd8fffdbe349”</td></tr><tr><td style="text-align:left">files</td><td style="text-align:left">传输的文件</td><td style="text-align:left"></td></tr><tr><td style="text-align:left">headers</td><td style="text-align:left">请求头</td><td style="text-align:left">Origin: <a href="http://localhost:63342" target="_blank" rel="noopener">http://localhost:63342</a><br>Content-Length: 3<br>Accept-Language: zh-CN,zh;q=0.9<br>Accept-Encoding: gzip, deflate<br>Connection: keep-alive<br>Accept: <em>/</em><br>User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36<br>Host: 10.126.1.209:8008<br>Referer: <a href="http://localhost:63342/consul/test.html?_ijt=7qq0e9dtrj0j7ikqsgee0qu9gq" target="_blank" rel="noopener">http://localhost:63342/consul/test.html?_ijt=7qq0e9dtrj0j7ikqsgee0qu9gq</a><br>Content-Type: application/x-www-form-urlencoded; charset=UTF-8</td></tr><tr><td style="text-align:left">host</td><td style="text-align:left">请求的主机</td><td style="text-align:left">10.126.1.209:8008</td></tr><tr><td style="text-align:left">host_name</td><td style="text-align:left">主机名</td><td style="text-align:left">10.126.1.209</td></tr><tr><td style="text-align:left">method</td><td style="text-align:left">请求的方法</td><td style="text-align:left">POST/GET</td></tr><tr><td style="text-align:left">path</td><td style="text-align:left">请求的资源路径,不带query参数</td><td style="text-align:left">/page</td></tr><tr><td style="text-align:left">protocol</td><td style="text-align:left">协议类型</td><td style="text-align:left">http/https</td></tr><tr><td style="text-align:left">query</td><td style="text-align:left">请求地址中query部分的参数</td><td style="text-align:left">x=123&y=222&x=234</td></tr><tr><td style="text-align:left">query_arguments</td><td style="text-align:left">类似于arguments,但是只包含query部分</td><td style="text-align:left">{‘y’: [‘222’], ‘x’: [‘123’, ‘234’]}</td></tr><tr><td style="text-align:left">remote_ip</td><td style="text-align:left">请求主机地址</td><td style="text-align:left">10.126.1.209</td></tr><tr><td style="text-align:left">version</td><td style="text-align:left">协议版本</td><td style="text-align:left">HTTP/1.1</td></tr></tbody></table><h3 id="2、-RequestHandler响应设置的一些方法"><a href="#2、-RequestHandler响应设置的一些方法" class="headerlink" title="2、 RequestHandler响应设置的一些方法"></a>2、 RequestHandler响应设置的一些方法</h3><h4 id="2-1-self-set-header-name-value-设置响应头"><a href="#2-1-self-set-header-name-value-设置响应头" class="headerlink" title="2.1 self.set_header(name,value)设置响应头"></a>2.1 <code>self.set_header(name,value)</code>设置响应头</h4><p><code>add_header(name,value)</code>也可设置响应头</p><h4 id="2-2-self-get-argument-name-default-获取请求参数"><a href="#2-2-self-get-argument-name-default-获取请求参数" class="headerlink" title="2.2 self.get_argument(name,[default])获取请求参数"></a>2.2 <code>self.get_argument(name,[default])</code>获取请求参数</h4><p>如果有多个值只获取最后一个值</p><h4 id="2-3-self-write-chunk-将内容写进response-body中去"><a href="#2-3-self-write-chunk-将内容写进response-body中去" class="headerlink" title="2.3 self.write(chunk)将内容写进response body中去"></a>2.3 <code>self.write(chunk)</code>将内容写进response body中去</h4><h4 id="2-4-self-finish-chunk-关闭连接"><a href="#2-4-self-finish-chunk-关闭连接" class="headerlink" title="2.4 self.finish([chunk])关闭连接"></a>2.4 <code>self.finish([chunk])</code>关闭连接</h4><h4 id="2-5-self-set-secure-cookie-name-value-expires-days-设置安全cookie"><a href="#2-5-self-set-secure-cookie-name-value-expires-days-设置安全cookie" class="headerlink" title="2.5 self.set_secure_cookie(name,value,expires_days,...)设置安全cookie"></a>2.5 <code>self.set_secure_cookie(name,value,expires_days,...)</code>设置安全cookie</h4><h4 id="2-6-self-write-error-status-code-kwargs-返回错误响应内容"><a href="#2-6-self-write-error-status-code-kwargs-返回错误响应内容" class="headerlink" title="2.6 self.write_error(status_code,kwargs)返回错误响应内容"></a>2.6 <code>self.write_error(status_code,kwargs)</code>返回错误响应内容</h4><h4 id="2-7-self-set-status-status-code-reason-返回指定响应码和响应描述"><a href="#2-7-self-set-status-status-code-reason-返回指定响应码和响应描述" class="headerlink" title="2.7 self.set_status(status_code,reason)返回指定响应码和响应描述"></a>2.7 <code>self.set_status(status_code,reason)</code>返回指定响应码和响应描述</h4><h4 id="2-8-self-clear-cookie-name-path-domain-清除指定cookie"><a href="#2-8-self-clear-cookie-name-path-domain-清除指定cookie" class="headerlink" title="2.8 self.clear_cookie(name,path,domain)清除指定cookie"></a>2.8 <code>self.clear_cookie(name,path,domain)</code>清除指定cookie</h4><h4 id="2-9-self-redirect-url-permanent-status-重定向到指定url"><a href="#2-9-self-redirect-url-permanent-status-重定向到指定url" class="headerlink" title="2.9 self.redirect(url,permanent,status)重定向到指定url"></a>2.9 <code>self.redirect(url,permanent,status)</code>重定向到指定url</h4><h4 id="2-10-self-get-secure-cookie-name-value-获取安全cookie"><a href="#2-10-self-get-secure-cookie-name-value-获取安全cookie" class="headerlink" title="2.10 self.get_secure_cookie(name,value,...)获取安全cookie"></a>2.10 <code>self.get_secure_cookie(name,value,...)</code>获取安全cookie</h4><h4 id="2-11-self-on-finish-响应发送到客户端,关闭连接后调用"><a href="#2-11-self-on-finish-响应发送到客户端,关闭连接后调用" class="headerlink" title="2.11 self.on_finish()响应发送到客户端,关闭连接后调用"></a>2.11 <code>self.on_finish()</code>响应发送到客户端,关闭连接后调用</h4><h4 id="2-12-self-render-templateName-kwargs-渲染模板并作为响应内容"><a href="#2-12-self-render-templateName-kwargs-渲染模板并作为响应内容" class="headerlink" title="2.12 self.render(templateName,kwargs)渲染模板并作为响应内容"></a>2.12 <code>self.render(templateName,kwargs)</code>渲染模板并作为响应内容</h4><style>table th:first-of-type { width: 150px;}table th:nth-child(2){ width:200px;}table td{ word-break:break-all;}</style>]]></content>
<summary type="html">
<p>RequestHandler是tornado处理http请求的基类。对于一个http请求,使用此类获取请求的内容,并定制其响应内容。下面总结一下该类所包含的方法或变量。</p>
<h3 id="1、-self-request对象包含请求中的所有信息"><a href="#1
</summary>
<category term="tornado" scheme="http://yoursite.com/categories/tornado/"/>
<category term="RequestHandler" scheme="http://yoursite.com/tags/RequestHandler/"/>
</entry>
<entry>
<title>解决SQLAlchemy间隔长时间重连,提示MySQL server has gone away</title>
<link href="http://yoursite.com/2018/08/01/%E8%A7%A3%E5%86%B3SQLAlchemy%E9%97%B4%E9%9A%94%E9%95%BF%E6%97%B6%E9%97%B4%E9%87%8D%E8%BF%9E%EF%BC%8C%E6%8F%90%E7%A4%BAMySQL-server-has-gone-away/"/>
<id>http://yoursite.com/2018/08/01/解决SQLAlchemy间隔长时间重连,提示MySQL-server-has-gone-away/</id>
<published>2018-08-01T02:33:43.000Z</published>
<updated>2018-08-01T02:55:58.556Z</updated>
<content type="html"><![CDATA[<p>在构建tornado应用,采用SQLAlchemy作为ORM是一个比较不错的选择。使用SQLAlchemy,一般第一步需要用create_engine创建engine,但在一段时间不使用engine的时候,下次连接时会提示[2006]MySQL server has gone away的错误!如下图:<br><img src="/upload/sqlalchemy_error1.png" alt="imgName"></p><p>解决方法: </p><pre><code>from sqlalchemy import create_engine, eventfrom sqlalchemy.exc import DisconnectionErrordef checkout_listener(dbapi_con, con_record, con_proxy): try: try: dbapi_con.ping(False) except TypeError: dbapi_con.ping() except dbapi_con.OperationalError as exc: if exc.args[0] in (2006, 2013, 2014, 2045, 2055): raise DisconnectionError() else: raisedb_engine = create_engine(DATABASE_CONNECTION_INFO, pool_size=100, pool_recycle=3600)event.listen(db_engine, 'checkout', checkout_listener)</code></pre><blockquote><p>参考文档:<a href="http://discorporate.us/jek/talks/SQLAlchemy-EuroPython2010.pdf" target="_blank" rel="noopener">http://discorporate.us/jek/talks/SQLAlchemy-EuroPython2010.pdf</a></p></blockquote>]]></content>
<summary type="html">
<p>在构建tornado应用,采用SQLAlchemy作为ORM是一个比较不错的选择。使用SQLAlchemy,一般第一步需要用create_engine创建engine,但在一段时间不使用engine的时候,下次连接时会提示[2006]MySQL server has gon
</summary>
<category term="SQLAlchemy" scheme="http://yoursite.com/categories/SQLAlchemy/"/>
<category term="问题记录" scheme="http://yoursite.com/categories/SQLAlchemy/%E9%97%AE%E9%A2%98%E8%AE%B0%E5%BD%95/"/>
<category term="ORM" scheme="http://yoursite.com/tags/ORM/"/>
</entry>
<entry>
<title>利用libsvm识别图形验证码</title>
<link href="http://yoursite.com/2018/07/23/%E5%88%A9%E7%94%A8libsvm%E8%AF%86%E5%88%AB%E5%9B%BE%E5%BD%A2%E9%AA%8C%E8%AF%81%E7%A0%81/"/>
<id>http://yoursite.com/2018/07/23/利用libsvm识别图形验证码/</id>
<published>2018-07-23T01:23:51.000Z</published>
<updated>2018-07-23T02:53:59.544Z</updated>
<content type="html"><![CDATA[<p>  IBSVM软件包是台湾大学林智仁(Chih-Jen Lin)博士等用C++实现的LIBSVM库,可以说是使用最方便的SVM训练工具[71]。可以解决分类问题(包括C-SVC、n-SVC)、回归问题(包括e-SVR、n-SVR)以及分布估计(one-class-SVM )等问题,提供了线性、多项式、径向基和S形函数四种常用的核函数供选择,可以有效地解决多类问题、交叉验证选择参数、对不平衡样本加权、多类问题的概率估计等。 </p><pre><code># coding:utf-8import urllib.requestimport cairosvg, cv2from svmutil import *from PIL import ImagecaptchaUrl = "https://xxx.com" + "/server?model=captcha&action=getCaptcha"req = urllib.request.Request(captchaUrl)res = urllib.request.urlopen(req)svg = res.read()def svgToPng(svgSource, outputName): cairosvg.svg2png(bytestring=svgSource, write_to="temp\\%s" % outputName)def _get_dynamic_binary_image(tag): im = cv2.imread("temp\\%s.png"%tag) im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY) # 灰值化 # 二值化 th1 = cv2.adaptiveThreshold(im, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 21, 1) cv2.imwrite("temp\\%s-binary.png"%tag, th1) return th1def corpCaptcha(tag): image = cv2.imread("temp\\%s-binary.png"%tag) cv2.imwrite("temp\\%s_p1.png"%tag, image[:, 10:40]) cv2.imwrite("temp\\%s_p2.png"%tag, image[:, 30:60]) cv2.imwrite("temp\\%s_p3.png"%tag, image[:, 55:85]) cv2.imwrite("temp\\%s_p4.png"%tag, image[:, 80:110])def computeTestData(tag): img1 = Image.open("temp\\%s_p1.png"%tag) img2 = Image.open("temp\\%s_p2.png"%tag) img3 = Image.open("temp\\%s_p3.png"%tag) img4 = Image.open("temp\\%s_p4.png"%tag) with open("temp\\%s_data.txt"%tag,"w") as f: f.write(get_feature(img1,0)+"\n") f.write(get_feature(img2,0)+"\n") f.write(get_feature(img3,0)+"\n") f.write(get_feature(img4,0))def get_feature(img,label): width, height = img.size pixel_cnt_list = [] for y in range(height): pix_cnt_x = 0 for x in range(width): if img.getpixel((x, y)) == (0,0,0): # 黑色点 pix_cnt_x += 1 pixel_cnt_list.append(pix_cnt_x) for x in range(width): pix_cnt_y = 0 for y in range(height): if img.getpixel((x, y)) == (0,0,0): # 黑色点 pix_cnt_y += 1 pixel_cnt_list.append(pix_cnt_y) return "%d "%label+" ".join(["%d:%s"%(i,j) for i,j in enumerate(pixel_cnt_list,1)])def recognCaptcha(svg,tag): svgToPng(svg, "%s.png"%tag) _get_dynamic_binary_image(tag) corpCaptcha(tag) computeTestData(tag) m = svm_load_model('captcha.model') # 读取模型 y, x = svm_read_problem("temp\\%s_data.txt"%tag) p_label, p_acc, p_val = svm_predict(y, x, m) return p_labeldef asciiDecode(codeList): m =map(lambda x:chr(int(x)),codeList) ret = [] for i in m: ret.append(i) return "".join(ret)def learn(tag,rightCode,local): #rightCode=["a","G"] local=[1,2] with open("train_data.txt", "a") as f: for j,i in enumerate(local): if rightCode[j]!="o": img = Image.open("temp\\%s_p%d.png" % (tag,i)) f.write("\n"+get_feature(img, ord(rightCode[j]))) y,x = svm_read_problem("train_data.txt") m = svm_train(y,x,"-t 0 -c 4 -b 1") svm_save_model('captcha.model', m) #保存模型if __name__ == '__main__': args = sys.argv if args[1]=="test": code = recognCaptcha(svg, "xy") print(asciiDecode(code)) elif args[1]=="learn": learn("xy", args[2], [1, 2, 3, 4]) m = svm_load_model('captcha.model') # 读取模型 y, x = svm_read_problem("temp\\xy_data.txt") p_label, p_acc, p_val = svm_predict(y, x, m) print(asciiDecode(p_label)) elif args[1]=="reload": if len(args)<3: config = "" else: config = args[2:] y, x = svm_read_problem("train_data.txt") m = svm_train(y, x,config) #最优参数: -t 0 -c 4 -b 1 svm_save_model('captcha.model', m) # 保存模型</code></pre>]]></content>
<summary type="html">
<p>&emsp;&emsp;IBSVM软件包是台湾大学林智仁(Chih-Jen Lin)博士等用C++实现的LIBSVM库,可以说是使用最方便的SVM训练工具[71]。可以解决分类问题(包括C-SVC、n-SVC)、回归问题(包括e-SVR、n-SVR)以及分布估计(one-c
</summary>
<category term="机器学习" scheme="http://yoursite.com/categories/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="libsvm" scheme="http://yoursite.com/tags/libsvm/"/>
<category term="验证码识别" scheme="http://yoursite.com/tags/%E9%AA%8C%E8%AF%81%E7%A0%81%E8%AF%86%E5%88%AB/"/>
</entry>
<entry>
<title>python实现常用加密算法</title>
<link href="http://yoursite.com/2018/07/18/python%E5%AE%9E%E7%8E%B0%E5%B8%B8%E7%94%A8%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/"/>
<id>http://yoursite.com/2018/07/18/python实现常用加密算法/</id>
<published>2018-07-18T01:15:22.000Z</published>
<updated>2019-10-14T09:57:04.402Z</updated>
<content type="html"><![CDATA[<pre><code>需要用到的模块有系统自带的hashlib、base64、hmac,以及第三方模块pyCrypto</code></pre><h4 id="1、MD5加密"><a href="#1、MD5加密" class="headerlink" title="1、MD5加密"></a>1、MD5加密</h4><pre><code>import hashlibdef md5(data): m = hashlib.md5() m.update(data) return m.hexdigest()</code></pre><h4 id="2、SHA256加密"><a href="#2、SHA256加密" class="headerlink" title="2、SHA256加密"></a>2、SHA256加密</h4><p>SHA256也称为HMAC_SHA256,hmac是Hash-based Message Authentication Code的简写,就是指哈希消息认证码,包含有很多种哈希加密算法,sha256是其中一种。</p><pre><code>import hashlib,base64,hmacdef hmac_sha256_encrypt(message,secret): message = bytes(message).encode('utf-8') secret = bytes(secret).encode('utf-8') signature = base64.b64encode(hmac.new(secret, message, digestmod=hashlib.sha256).digest()) return signature</code></pre><h4 id="3、RSA加解密"><a href="#3、RSA加解密" class="headerlink" title="3、RSA加解密"></a>3、RSA加解密</h4><p>加密: </p><pre><code>from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5from Crypto.PublicKey import RSAimport base64,osdef rsa_encrypt(message): pem_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),"exts","public.pem") with open(pem_path) as f: key = f.read() rsakey = RSA.importKey(key) cipher = Cipher_pkcs1_v1_5.new(rsakey) cipher_text = base64.b64encode(cipher.encrypt(message)) return cipher_text</code></pre><p>解密: </p><pre><code>from Crypto import Randomfrom Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs1_v1_5from Crypto.PublicKey import RSAimport base64,osdef rsa_decrypt(encrypt_text): pem_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "exts", "private.pem") # 伪随机数生成器 random_generator = Random.new().read with open(pem_path) as f: key = f.read() rsakey = RSA.importKey(key) cipher = Cipher_pkcs1_v1_5.new(rsakey) text = cipher.decrypt(base64.b64decode(encrypt_text), random_generator) return text</code></pre><p>3.1 RSA公钥加密,私钥解密</p><pre><code>import base64from cryptography.hazmat.backends import default_backendfrom cryptography.hazmat.primitives import serializationfrom cryptography.hazmat.primitives.asymmetric import paddingdef RSA_Encrypt_by_publicKey(data,publicKeyPath): # 读取公钥数据 key_file = open(publicKeyPath, 'rb') key_data = key_file.read() key_file.close() # 从公钥数据中加载公钥 public_key = serialization.load_pem_public_key( key_data, backend=default_backend() ) # 使用公钥对原始数据进行加密,使用PKCS#1 v1.5的填充方式 out_data = public_key.encrypt( data.encode("utf-8"), padding.PKCS1v15() ) ret = base64.b64encode(out_data).decode("utf-8") # 返回加密结果 return retdef RSA_Decrypt_by_privateKey(msg,privateKeyPath): # 读取原始数据 data = base64.b64decode(msg.encode("utf-8")) # 读取私钥数据 key_file = open(privateKeyPath, 'rb') key_data = key_file.read() key_file.close() # 从私钥数据中加载私钥 private_key = serialization.load_pem_private_key( key_data, password=None, backend=default_backend() ) # 使用私钥对数据进行解密,使用PKCS#1 v1.5的填充方式 out_data = private_key.decrypt( data, padding.PKCS1v15() ) # 返回解密结果 return out_data.decode("utf-8")</code></pre><h4 id="4、SHA1withRSA加密"><a href="#4、SHA1withRSA加密" class="headerlink" title="4、SHA1withRSA加密"></a>4、SHA1withRSA加密</h4><p>这种加密方式常用于一般接口中做签名校验,isPem为True的时候,传入的是密钥文件格式,以—-BEGIN PRIVATE KEY—-开头的多行文本;isPem为False时,传入的是私钥字符串privateKey。 </p><pre><code>import base64from Crypto.Signature import PKCS1_v1_5from Crypto.PublicKey import RSAfrom Crypto.Hash import SHAdef sign(data,secret,isPem=False): if isPem: pem = secret else: pem = "-----BEGIN PRIVATE KEY-----\n"+"\n".join([secret[64*i:64*(i+1)] for i in range(len(secret)//64+1)])+"\n-----END PRIVATE KEY-----" private_key = RSA.importKey(pem) cipher = PKCS1_v1_5.new(private_key) h = SHA.new(data) signature = cipher.sign(h) return base64.b64encode(signature)</code></pre><h4 id="5、AES加解密"><a href="#5、AES加解密" class="headerlink" title="5、AES加解密"></a>5、AES加解密</h4><p>CBC模式:</p><pre><code>from Crypto.Cipher import AESimport base64def AES_CBC_encrypt(data, password, iv): BS = AES.block_size pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) cipher = AES.new(password.encode("utf-8"), AES.MODE_CBC, iv.encode("utf-8")) data = cipher.encrypt(pad(data.encode("utf-8"))) return base64.b64encode(data)def AES_CBC_decrypt(data, password, iv): iv = iv.encode("utf-8") data = iv + base64.b64decode(data.decode("utf-8")) bs = AES.block_size if len(data) <= bs: return data unpad = lambda s: s[0:-ord(s[-1])] iv = data[:bs] cipher = AES.new(password.encode("utf-8"), AES.MODE_CBC, iv) data = unpad(cipher.decrypt(data[bs:])) return data</code></pre><p>ECB模式:</p><pre><code>from Crypto.Cipher import AESimport base64def AES_ECB_encrypt(data, password="yyfax10086100861"): BS = AES.block_size pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS) cipher = AES.new(password.encode("utf-8"), AES.MODE_ECB) data = cipher.encrypt(pad(data.encode("utf-8"))) return base64.b64encode(data)def AES_ECB_decrypt(data, password="yyfax10086100861"): data = "0"*16 + base64.b64decode(data.decode("utf-8")) bs = AES.block_size if len(data) <= bs: return data unpad = lambda s: s[0:-ord(s[-1])] cipher = AES.new(password.encode("utf-8"), AES.MODE_ECB) data = unpad(cipher.decrypt(data[bs:])) return data</code></pre><h4 id="6、DES加解密"><a href="#6、DES加解密" class="headerlink" title="6、DES加解密"></a>6、DES加解密</h4><p>ECB模式:</p><pre><code>from pyDes import *import base64def des_ecb_decrypt(source, key): source = base64.b64decode(source) source = bytearray(source) source = [chr(x) for x in source] des_obj = des(' ', ECB, IV=None, pad=None, padmode=PAD_PKCS5) des_obj.setKey(key.encode('utf-8')) des_result = des_obj.decrypt(source) return des_resultdef des_ecb_encode(source, key): des_obj = des(' ', ECB, IV=None, pad=None, padmode=PAD_PKCS5) des_obj.setKey(key.encode('utf-8')) source = [chr(ord(x)) for x in source] des_result = des_obj.encrypt(source) return base64.b64encode(des_result)</code></pre>]]></content>
<summary type="html">
<pre><code>需要用到的模块有系统自带的hashlib、base64、hmac,以及
第三方模块pyCrypto
</code></pre><h4 id="1、MD5加密"><a href="#1、MD5加密" class="headerlink" title="1、MD
</summary>
<category term="python" scheme="http://yoursite.com/categories/python/"/>
<category term="crypto" scheme="http://yoursite.com/categories/python/crypto/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="加密算法" scheme="http://yoursite.com/tags/%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>tornado学习总结(2)</title>
<link href="http://yoursite.com/2018/07/02/tornado%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%EF%BC%882%EF%BC%89/"/>
<id>http://yoursite.com/2018/07/02/tornado学习总结(2)/</id>
<published>2018-07-02T03:28:21.000Z</published>
<updated>2018-08-27T02:06:16.211Z</updated>
<content type="html"><![CDATA[<p>  上一篇介绍tornado使用的基本知识点,这篇文章介绍tornado关键概念,以及目前经过几个项目优化后所采用的项目结构。 </p><h3 id="1-基础概念"><a href="#1-基础概念" class="headerlink" title="1. 基础概念"></a>1. 基础概念</h3><h4 id="1-1异步"><a href="#1-1异步" class="headerlink" title="1.1异步"></a>1.1异步</h4><p>  即将进行的操作需要请求其它系统或其它函数去执行,而且当前主进程并不(立即)关心它们的执行结果,只是提供一个处理返回结果的函数入口给它们使用<br>  <font color="#34A"><em>( eg. 去书店买书,请老板查找所需书是否存在,因为所有书的信息都已经录入电脑了,所以查找这个过程比较快,此时我就采用同步方式等待老板的查询结果,如果有的话就继续后面的买书过程;如果老板查询没有这本书,但是告诉我可以留下电话号码,等书进货到了,他会打电话通知我,我再选择购买方式,这个就是异步)</em></font></p><p>  异步通信(前后端请求响应)和异步处理(系统内部处理)是不一样的,当描述前后端通信的时候,异步指的是前段非阻塞方式,同步指的是前端阻塞方式。异步可以是非阻塞或阻塞的。</p><h5 id="1-1-1-回调函数-def-buyBook"><a href="#1-1-1-回调函数-def-buyBook" class="headerlink" title="1.1.1 回调函数 def buyBook():"></a>1.1.1 回调函数 def buyBook():</h5><pre><code>def buyBookWay(): ***do buy book on line***if queryBookIsExist(): ***do buy book off line***else: BookStore.stockBooks(tel,callback=buyBookWay)</code></pre><h5 id="1-1-2-协程-def-buyBook"><a href="#1-1-2-协程-def-buyBook" class="headerlink" title="1.1.2 协程 def buyBook():"></a>1.1.2 协程 def buyBook():</h5><pre><code>def buyBookWay(): ***do buy book on line***if queryBookIsExist(): ***do buy book off line***else: yield BookStore.stockBooks(tel) buyBookWay()</code></pre><h4 id="1-2-阻塞"><a href="#1-2-阻塞" class="headerlink" title="1.2 阻塞"></a>1.2 阻塞</h4><p>  当所需要的资源(如cpu,数据库,IO等)被其它事件占用时,就会造成当前处理过程被阻塞。<br>  tornado默认是单线程,在linux中tornado是基于epoll事件驱动框架,所以在网络时间上是无阻塞的,但是执行其它一些耗时操作的时候还是会阻塞其他请求</p><h3 id="2-项目结构"><a href="#2-项目结构" class="headerlink" title="2. 项目结构"></a>2. 项目结构</h3><pre><code>|--apps #应用目录 |--main #子应用 |--dao #数据库操作目录 |--dbCURD.py |--service #基础功能服务目录 |--bussiness.py |--handlers.py #url请求处理类目录(RequestHandler及其子类) |--models.py #数据库模型 |--tests.py #测试用例 |--urls.py #url路由定义 |--admin |--app_x|--exts #应用需要的其他外部文件放到此目录 |--data.dat |--private.pem |--public.pem |--libs #应用的第三方库放这里|--logs #日志文件目录|--plugin #公共组件服务目录 |--base.py |--logger.py|--testSuit #测试集目录|--static #静态文件目录,setting中配置static_path指定,引用时使用{{static_url('相对路径')}}。前后端分离应用可删除该目录 |--templates #模板文件目录,setting中配置template_path指定。前后端分离应用可删除该目录|--config.yml #配置文件|--server.py #应用入口,主执行文件|--urls.py #主路由文件|--setting.py #应用配置文件|--requirements.txt #依赖包文件|--initDB.py #数据库初始化文件,创建数据库表</code></pre><h3 id="3-文件"><a href="#3-文件" class="headerlink" title="3. 文件"></a>3. 文件</h3><blockquote><p>file: /server.py</p></blockquote><pre><code>#coding = utff-8import tornado.ioloopimport tornado.httpserverimport tornado.optionsimport tornado.webfrom urls import urlsfrom setting import settingsfrom tornado.options import define,optionsapplication = tornado.web.Application( handlers=urls, **settings)define("port",default=8000,help="run on the given prot",type = int)def main(): tornado.options.parse_command_line() http_server = tornado.httpserver.HTTPServer(application,xheaders=True) # http_server.listen(options.port) #上面修改成下面可以使用多进程运行,windows下os.fork()无法执行,cpu数量为1 http_server.bind(options.port) http_server.start(1) #1指的是1个CPU进程,0表示根据CPU核心数量自动决定进程数量 print "Development server is running at http://127.0.0.1:%s"%options.port print "Quit the server with Control-C" tornado.ioloop.IOLoop.instance().start()if __name__ == "__main__": main()</code></pre><blockquote><p>file: /urls.py</p></blockquote><pre><code># coding=utf-8from importlib import import_modulefrom tornado.web import URLSpecimport sysreload(sys)sys.setdefaultencoding("utf-8")def include(module): res = import_module(module) urls = getattr(res, 'urls', res) return urlsdef url_wrapper(urls): """ 接受一个数组格式的参数,数组元素类型可以是以下几种情况: 1、 (r'/a/', include('apps.main.urls'),"main"), 2、 (r'/x',TestHandler,"test"), 3、 URLSpec(r'/x',TestHandler,name="test"), """ wrapper_list = [] for url in urls: if isinstance(url,URLSpec): path, handles, name = url.regex.pattern,url.handler_class,url.name else: path, handles = url[0],url[1] name = None if len(url)<3 else url[2] if isinstance(handles, (tuple, list)): #如果是include,则包含的handles是元组或列表形式 for handle in handles: if isinstance(handle, URLSpec): pattern, handle_class, url_name = handle.regex.pattern, handle.handler_class, handle.name else: pattern, handle_class = handle[0],handle[1] url_name = None if len(handle)<3 else handle[2] if name==None: retname = url_name #如果外层name为None,则reverse_url直接使用内层name else: retname = name+'_'+url_name if url_name else None #如果外层name不为None,内层name为空则不定义name,即无法被reverse_url使用,内层不为空则reverse_url调用名称为【外层name_内层name】 wrapper_list.append(URLSpec('{0}{1}'.format(path, pattern), handle_class,name=retname)) else: wrapper_list.append(URLSpec(path, handles,name=name)) return wrapper_listurls = url_wrapper([ (r'/main/',include('apps.main.urls'),"api"), (r'/admin/',include('apps.admin.urls'),"admin") ])</code></pre><blockquote><p>file: setting.py</p></blockquote><pre><code>#coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf-8')import os,base64,uuidfrom sqlalchemy import create_enginefrom sqlalchemy.ext.declarative import declarative_basefrom plugin.base import getCfgValueINSTALLED_APPS=[ "apps.main", "apps.admin"]DATABASE = { 'DBDRIVER':'mysqldb', 'NAME':getCfgValue('mysql','dbname'), 'USER':getCfgValue('mysql','user'), 'PASSWORD':getCfgValue('mysql','password'), 'HOST':getCfgValue('mysql','host'), 'PORT':getCfgValue('mysql','port')}Base = declarative_base()engine = create_engine('mysql+{DBDRIVER}://{USER}:{PASSWORD}@{HOST}:{PORT}/{NAME}?charset=utf8'.format(**DATABASE),pool_recycle=3600)settings=dict( template_path = os.path.join(os.path.dirname(__file__),"templates"), static_path = os.path.join(os.path.dirname(__file__),"statics"), debug = False, cookie_secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), #用来使用get_secure_cookie方法 xsrf_cookies = False, login_url = '/mockServer/login',)</code></pre><blockquote><p>file: /plugin/logger.py</p></blockquote><pre><code>#coding=utf-8import os,datetime,syslog_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),"logs")def log(level,content,where): now = str(datetime.datetime.today()) today = str(datetime.datetime.today().date()) filePath = os.path.join(log_path,'log_'+today+'.log') isErr = False try: content = content.decode("utf-8") except: isErr = True if isErr: try: content = content.decode(sys.getfilesystemencoding()) except: pass with open(filePath,'a') as f: f.write(('[%s][%s]%s [IN %s]\n'%(level,now,content,where)).encode(sys.getfilesystemencoding()))def error(msg,where='unknown'): log('ERROR',msg,where)def info(msg,where='unknown'): log('INFO', msg, where)def warning(msg,where='unknown'): log('WARNING', msg, where)if __name__=='__main__': error('你的文件没有访问权限!','line 15-line 19') info('访问http://www.baidu.com') warning('删除/x/y/z成功!')</code></pre><blockquote><p>file: /apps/xxx/models.py</p></blockquote><pre><code>#coding=utf-8import sysreload(sys)sys.setdefaultencoding('utf-8')from sqlalchemy import Tablefrom sqlalchemy import Column,String,Integer,Text,ForeignKey,DateTime#常用字段类型有String,Integer,Text,Boolean,SmallInteger,DateTimefrom sqlalchemy.orm import relationshipfrom setting import Basefrom datetime import datetimeclass User(Base): __tablename__ = 'users' id = Column(Integer, primary_key=True) username = Column(String(64), nullable=False, index=True) password = Column(String(64), nullable=False) email = Column(String(64), nullable=False, index=True) articles = relationship('Article', backref='author') #relationship只描述关系,不定义字段 def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.username)class UserInfo(Base): __tablename__ = 'userinfos' id = Column(Integer, primary_key=True) name = Column(String(64)) qq = Column(String(11)) phone = Column(String(11)) link = Column(String(64)) user_id = Column(Integer, ForeignKey('users.id')) #多方需要定义外键字段,指定唯一值(为什么不在一对多的唯一值方定义?因为那样子就无法从一查找到具体值) user = relationship('User', backref='userinfo', uselist=False) #定义一对一关系class Article(Base): __tablename__ = 'articles' id = Column(Integer, primary_key=True) title = Column(String(255), nullable=False, index=True) content = Column(Text) user_id = Column(Integer, ForeignKey('users.id')) cate_id = Column(Integer, ForeignKey('categories.id')) tags = relationship('Tag', secondary='article_tag', backref='articles') createtime = Column(DateTime, default=datetime.now) updatetime = Column(DateTime, default=datetime.now, onupdate=datetime.now) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.title)class Category(Base): __tablename__ = 'categories' id = Column(Integer, primary_key=True) name = Column(String(64), nullable=False, index=True) articles = relationship('Article', backref='category') def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.name)## 额外表,用来定义多对多关系 ###article_tag = Table( 'article_tag', Base.metadata, Column('article_id', Integer, ForeignKey('articles.id')), Column('tag_id', Integer, ForeignKey('tags.id')))class Tag(Base): __tablename__ = 'tags' id = Column(Integer, primary_key=True) name = Column(String(64), nullable=False, index=True) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.name)if __name__ == "__main__": pass</code></pre><blockquote><p>file: /apps/xxx/urls.py</p></blockquote><pre><code># coding=utf-8from handlers import IndexHandler, LoginHandler, NewProjectHandler, ListAllProjectsHandler, NewModuleHandler, \ GetProjectNameHandler, ListProjectModulesHandler, NewApiHandler, ListProjectApis, GetProjectDescHandler, \ ListApisByMidHandler, GetApiDataHandler, SaveApiDataHandler, ListAllResponseTypesHandler, GetConsulEnvInfoHandler, \ SaveConsulEnvInfoHandler,GetAuthInfoHandler,LogoutHandlerfrom tornado.web import urlurls = [ url(r'', IndexHandler, name="index"), url(r'login', LoginHandler, name='login'), url(r'logout', LogoutHandler, name='logout'), url(r'getauthinfo', GetAuthInfoHandler, 'getauthinfo'), url(r'newProject', NewProjectHandler, name='newProject'), url(r'listAllProjects', ListAllProjectsHandler, name='listAllProjects'), url(r'newModule', NewModuleHandler, name='newModule'), url(r'getProjectName', GetProjectNameHandler, name='getProjectName'), url(r'listProjectModules', ListProjectModulesHandler, name='listProjectModules'), url(r'newApi', NewApiHandler, name='newApi'), url(r'listProjectApis', ListProjectApis, name='listProjectApis'), url(r'getProjectDesc', GetProjectDescHandler, name='getProjectDesc'), url(r'listApisByMid', ListApisByMidHandler, name='listApisByMid'), url(r'getApiData', GetApiDataHandler, name='getApiData'), url(r'saveApiData', SaveApiDataHandler, name='saveApiData'), url(r'listAllResponseTypes', ListAllResponseTypesHandler, name='listAllResponseTypes'), url(r'getConsulEnvInfo', GetConsulEnvInfoHandler, name='getConsulEnvInfo'), url(r'saveConsulEnvInfo', SaveConsulEnvInfoHandler, name='saveConsulEnvInfo'),]</code></pre><blockquote><p>file: /apps/xxx/handlers.py</p></blockquote><pre><code># coding=utf-8import tornado.webimport tornado.concurrentimport tornado.genfrom concurrent.futures import ThreadPoolExecutorfrom plugin import loggerfrom dao.main_curd import *import sys, time, jsonfrom tornado.httpclient import HTTPRequesttry: from tornado.curl_httpclient import CurlAsyncHTTPClient as AsyncHTTPClientexcept ImportError: from tornado.simple_httpclient import SimpleAsyncHTTPClient as AsyncHTTPClientreload(sys)sys.setdefaultencoding('utf8')_result = {} # 存储格式为:_result[tid]={'status': 'success', 'msg': context}TIMEOUT = 30MAX_WORKERS = 50class BaseHandler(tornado.web.RequestHandler): executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) def get_current_user(self): return self.get_secure_cookie("user")######### handler类异步处理编写方法 ##################前端同步获取结果####class Test1Handler(BaseHandler): @tornado.concurrent.run_on_executor def background_task(self): # do some thing asynchronously res = 'hello,world' return res @tornado.gen.coroutine def get(self): res = yield self.background_task() self.write(res)####前端异步获取结果######第一步,通知服务器执行处理,并生成、存储tid,并返回tid到前端class Test2Handler(BaseHandler): @tornado.concurrent.run_on_executor def background_task(self, tid): try: # do some thing asynchronously res = {'status': 'success', 'msg': ''} except Exception, e: res = {'status': 'failed', 'msg': e.message} _result[tid] = res @tornado.gen.coroutine def get(self): tid = str(int(time.time() * 10000)) yield self.background_task(tid) self.write(tid) @tornado.gen.coroutine def post(self): tid = str(int(time.time() * 10000)) yield self.background_task(tid) self.write(tid)# 第二步,根据tid查询结果内容class AsynGetResultHandler(BaseHandler): @tornado.concurrent.run_on_executor def background_task(self, tid, timeout): start = time.time() while not tid in _result.keys(): if time.time() - start > timeout: break time.sleep(0.2) if tid in _result.keys(): out = _result[tid] # 结果 del _result[tid] # 删除tid的数据。 return out else: return "timeout." @tornado.gen.coroutine def get(self, timeout=TIMEOUT): tid = self.get_argument("tid") res = yield self.background_task(tid, timeout) self.write(res)</code></pre><blockquote><p>file: /apps/xxx/dao/dbCURD.py</p></blockquote><pre><code># coding=utf-8import sys, jsonreload(sys)sys.setdefaultencoding('utf-8')from setting import enginefrom sqlalchemy.orm import sessionmakerfrom apps.main.service.consulOPT import getService,getAllService,newAgentfrom apps.main.models import *Session = sessionmaker(bind=engine)#############新增、创建#####################def addUser(account, passwd, email, role, name='', status=True): session = Session() user = User(account=account, name=name, passwd=passwd, email=email, role=role, status=status) session.add(user) try: session.commit() except Exception, e: session.rollback() raise e finally: ret = user.id session.close() return ret</code></pre>]]></content>
<summary type="html">
<p>&emsp;&emsp;上一篇介绍tornado使用的基本知识点,这篇文章介绍tornado关键概念,以及目前经过几个项目优化后所采用的项目结构。 </p>
<h3 id="1-基础概念"><a href="#1-基础概念" class="headerlink" titl
</summary>
<category term="tornado" scheme="http://yoursite.com/categories/tornado/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="tornado" scheme="http://yoursite.com/tags/tornado/"/>
<category term="webServer" scheme="http://yoursite.com/tags/webServer/"/>
</entry>
<entry>
<title>tornado学习总结(1)</title>
<link href="http://yoursite.com/2018/07/02/tornado%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%EF%BC%881%EF%BC%89/"/>
<id>http://yoursite.com/2018/07/02/tornado学习总结(1)/</id>
<published>2018-07-02T01:49:30.000Z</published>
<updated>2018-07-02T07:05:20.573Z</updated>
<content type="html"><![CDATA[<p>这篇文章主要记录Tornado基本知识,以后知识点忘了可做查询用。Tornado大体上可以被分为4个主要部分: </p><ul><li>web框架(包括创建web应用的<code>RequestHandler</code>类,还有很多其他支持的类) </li><li>HTTP客户端和服务端实现 (HTTPServer、AsyncHTTPClient)</li><li>异步网络库(IOLoop、IOStream),为HTTP组件提供构建模块,也可以用来实现其他协议</li><li>携程库(tornado.gen),允许异步代码写的更直接而不用链式回调的方式 </li></ul><hr><h3 id="基础知识"><a href="#基础知识" class="headerlink" title="基础知识"></a>基础知识</h3><ol><li><h5 id="定义urls-并将urls绑定到处理类:"><a href="#定义urls-并将urls绑定到处理类:" class="headerlink" title="定义urls,并将urls绑定到处理类:"></a>定义urls,并将urls绑定到处理类:</h5><p> <code>app = tornado.web.Application(handlers=[(r"/",IndexHandler)])</code><br> Application可以接受更多的配置项: </p><pre><code>settings=dict( template_path = os.path.join(os.path.dirname(__file__),"templates"), static_path = os.path.join(os.path.dirname(__file__),"statics"), debug = False, cookie_secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes), #用来使用get_secure_cookie方法 xsrf_cookies = False, login_url = '/mockServer/login')urls = [(r"/",IndexHandler)]application = tornado.web.Application(handlers=urls,**settings) </code></pre></li><li><h5 id="绑定app-包含各种url信息-到http-server:"><a href="#绑定app-包含各种url信息-到http-server:" class="headerlink" title="绑定app(包含各种url信息)到http_server:"></a>绑定app(包含各种url信息)到http_server:</h5><p> <code>http_server = tornado.httpserver.HTTPServer(app)</code> </p></li><li><h5 id="启动server监听(指定端口):"><a href="#启动server监听(指定端口):" class="headerlink" title="启动server监听(指定端口):"></a>启动server监听(指定端口):</h5><p> <code>http_server.listen(options.port)</code> </p></li><li><h5 id="加入到循环事务中-并启动事务:"><a href="#加入到循环事务中-并启动事务:" class="headerlink" title="加入到循环事务中,并启动事务:"></a>加入到循环事务中,并启动事务:</h5><p> <code>tornado.ioloop.IOLoop.instance().start()</code> </p></li><li><h5 id="处理类IndexHandler继承自tornado-web-RequestHandler-需要覆盖其get或post等方法。写内容到网页可以用:-self-write-字符串-,或者self-render-模版文件-key1-value1-模版文件中使用来引用对象-value1可以是list等复杂类型"><a href="#处理类IndexHandler继承自tornado-web-RequestHandler-需要覆盖其get或post等方法。写内容到网页可以用:-self-write-字符串-,或者self-render-模版文件-key1-value1-模版文件中使用来引用对象-value1可以是list等复杂类型" class="headerlink" title="处理类IndexHandler继承自tornado.web.RequestHandler,需要覆盖其get或post等方法。写内容到网页可以用: self.write(字符串),或者self.render(模版文件,key1=value1),模版文件中使用来引用对象,value1可以是list等复杂类型"></a>处理类IndexHandler继承自tornado.web.RequestHandler,需要覆盖其get或post等方法。写内容到网页可以用: self.write(字符串),或者self.render(模版文件,key1=value1),模版文件中使用来引用对象,value1可以是list等复杂类型</h5></li><li><h5 id="RequestHandler类有读写cookie的方法:"><a href="#RequestHandler类有读写cookie的方法:" class="headerlink" title="RequestHandler类有读写cookie的方法:"></a>RequestHandler类有读写cookie的方法:</h5><p> <code>set_cookie()</code> / <code>get_cookie()</code><br> 使用更安全的方式:<code>set_secure_cookie()</code> / <code>get_secure_cookie()</code> ,此时需要在Application的setting中增加如下设置:<br> <code>cookie_secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)</code><br> (<code>set_secure_cookie</code>中加上<code>httponly=True, secure=True</code>更加安全)<br> 清理指定cookie:<code>clear_cookie('指定名称')</code></p></li><li><h5 id="开启-XSRF-保护:"><a href="#开启-XSRF-保护:" class="headerlink" title="开启 XSRF 保护:"></a>开启 XSRF 保护:</h5><p> a) 在Application的setting中增加如下设置:<code>xsrf_cookies = True</code><br> b) 在模板文件中的表单里面加入如下标记:</p><pre><code>{% raw xsrf_form_html () %}</code></pre><p> c) 在ajax中增加标记:</p><pre><code>function getCookie(name){ var x = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return x ? x[1]:undefined;}$(document).ready(function(){ $("#login").click(function(){ var user = $("#username").val(); var pwd = $("#password").val(); var pd = {"username":user, "password":pwd, "_xsrf":getCookie("_xsrf")}; $.ajax({ type:"post", url:"/", async:true, data:pd, cache:false, success:function(data){ window.location.href = "/user?user="+data; }, error:function(){ alert("error!"); }, }); });}); </code></pre></li><li><h5 id="json和python对象互相转换:"><a href="#json和python对象互相转换:" class="headerlink" title="json和python对象互相转换:"></a>json和python对象互相转换:</h5><p> <code>tornado.escape.json_encode</code>(python对象)==>json字符串<br> <code>tornado.escape.json_decode</code>(json字符串)==>python对象<br> 它们与json 模块中的 dump()、load()功能相仿。 </p></li><li><h5 id="用户认证:"><a href="#用户认证:" class="headerlink" title="用户认证:"></a>用户认证:</h5><p> a) 具体的Handler的get方法加装饰器:<code>@tornado.web.authenticated</code><br> b) 可以使用<code>self.current_user</code>变量获取当前用户<br> c) a/b中的用法实际都会调用当前Handler类的<code>get_current_user</code>方法,所以前提是需要重载<code>get_current_user</code>方法,才可以使用。一般编写<code>BaseHandler(tornado.web.RequestHandler)</code>基类,里面实现<code>get_current_user</code>方法的重载,然后其他类继承自BaseHandler类<br> d) 若当前用户不存在(即<code>get_current_user</code>返回None时),使用调用装饰器或者<code>current_user</code>变量的时候,会寻找Application里setting的<code>login_url</code>指定的路径 </p></li><li><h5 id="异步与阻塞:"><a href="#异步与阻塞:" class="headerlink" title="异步与阻塞:"></a>异步与阻塞:</h5><p>【异步阻塞】 </p><pre><code>a) 设置异步函数结束后不断开服务器连接,使用`@tornado.web.asynchronous`装饰器。(原理是设置`_auto_finish`值为False,直到执行到`self.finish()`才关闭连接) b) 使用`tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 17, callback=self.on_response)` 设置超时时间,以及所要执行的回调函数 C) 编写回调函数,并在函数最后加上`self.finish()`调用 class SleepHandler(BaseHandler): @tornado.web.asynchronous def get(self): tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 17, callback=self.on_response) def on_response(self): self.render("sleep.html") self.finish() =======另一种写法========= class SleepHandler(tornado.web.RequestHandler): @tornado.gen.coroutine def get(self): yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.time() + 17) #yield tornado.gen.sleep(17) self.render("sleep.html")</code></pre><p>【异步非阻塞】 </p><pre><code>python2.7需要安装concurrent模块:`@tornado.concurrent.run_on_executor` TIMEOUT = 30MAX_WORKERS = 50class BaseHandler(tornado.web.RequestHandler): executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) </code></pre></li><li><h5 id="异步函数编写:"><a href="#异步函数编写:" class="headerlink" title="异步函数编写:"></a>异步函数编写:</h5><pre><code>@tornado.gen.coroutine def sleep(self): yield tornado.gen.sleep(10) raise tornado.gen.Return('hello world!')</code></pre></li><li><h5 id="完整实例:"><a href="#完整实例:" class="headerlink" title="完整实例:"></a>完整实例:</h5><pre><code>#coding=utf-8import tornado.httpserverimport tornado.ioloopimport tornado.optionsimport tornado.webfrom tornado.options import define,optionsdefine("port",default=8000,help="run on the given port",type=int)class IndexHandler(tornado.web.RequestHandler): def get(self): greeting = self.get_argument('greeting','Hello') #get_argument返回的是unicode编码 self.write(greeting+',world!')if __name__=="__main__": tornado.options.parse_command_line() app = tornado.web.Application(handlers=[(r"/",IndexHandler)]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(options.port) tornado.ioloop.IOLoop.instance().start() </code></pre><p>• <strong>tornado.httpserver</strong>:这个模块就是用来解决 web 服务器的 http 协议问题,它提供了不少属性方法,实现客户端和服务器端的互通。Tornado 的非阻塞、单线程的特点在这个模块中体现。<br>• <strong>tornado.ioloop</strong>:这个也非常重要,能够实现非阻塞 socket 循环,不能互通一次就结束呀。<br>• <strong>tornado.options</strong>:这是命令行解析模块,也常用到。执行终端help命令时出现的提示。<br>• <strong>tornado.web</strong>:这是必不可少的模块,它提供了一个简单的 Web 框架与异步功能,从而使其扩展到大量打开的连接,使其成为理想的长轮询。 </p></li><li><h5 id="模版:"><a href="#模版:" class="headerlink" title="模版:"></a>模版:</h5><p>1) for循环: </p><pre><code>{% for i in key1 %} {{i[0]}}{% end %}</code></pre><p>2) 书写python表达式:<br>  使用双括号:<code>7</code><br>3) 调试模板:</p><pre><code>from tornado.template import Templateprint Template("{{ 'python'[0:2] }}").generate() #输出py </code></pre><p>4) 编写python语句:for,if,try,while等,可以多层嵌套 </p><pre><code>{% 语句 %} ....{% end %}</code></pre><p>5) 设置变量:<code></code><br>6) 对变量x不进行html转义: </p><pre><code>{% raw x %} </code></pre><p>7) 常用模板函数: </p><pre><code>escape(s):替换字符串 s 中的 &、<、> 为他们对应的 HTML 字符。url_escape(s):使用 urllib.quote_plus 替换字符串 s 中的字符为 URL 编码形式。json_encode(val):将 val 编码成 JSON 格式。squeeze(s):过滤字符串 s,把连续的多个空白字符替换成一个空格。 </code></pre></li><li><h5 id="模板继承:"><a href="#模板继承:" class="headerlink" title="模板继承:"></a>模板继承:</h5><p>1) 父模板:</p><pre><code>{% block 继承块名称 %} 默认内容{% end %}</code></pre><p>2) 子模板:</p><pre><code>{% extends "base.html" %}{% block 继承块名称 %} 更新内容{% end %}</code></pre></li><li><h5 id="模板组件:"><a href="#模板组件:" class="headerlink" title="模板组件:"></a>模板组件:</h5><p>模板中引入其它模板:</p><pre><code>{% module Template("message.html", message=message) %}</code></pre></li></ol>]]></content>
<summary type="html">
<p>这篇文章主要记录Tornado基本知识,以后知识点忘了可做查询用。Tornado大体上可以被分为4个主要部分: </p>
<ul>
<li>web框架(包括创建web应用的<code>RequestHandler</code>类,还有很多其他支持的类) </li>
<
</summary>
<category term="tornado" scheme="http://yoursite.com/categories/tornado/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="tornado" scheme="http://yoursite.com/tags/tornado/"/>
<category term="webServer" scheme="http://yoursite.com/tags/webServer/"/>
</entry>
<entry>
<title>vue.js全局消息组件</title>
<link href="http://yoursite.com/2018/06/28/vue-js%E5%85%A8%E5%B1%80%E6%B6%88%E6%81%AF%E7%BB%84%E4%BB%B6/"/>
<id>http://yoursite.com/2018/06/28/vue-js全局消息组件/</id>
<published>2018-06-28T02:34:08.000Z</published>
<updated>2018-06-28T03:01:02.059Z</updated>
<content type="html"><![CDATA[<p>  这里总结了一个简单的实现全局消息通知的方法,实现的效果如下图:<br><img src="/upload/global_message_notice.png" alt="imgName"><br>1、修改<code>/src/App.vue</code>文件,<template\>中增加消息控件,放在<code><router-view/></code>后面 </template\></p><pre><code><div class="ant-message"> <div class="ant-content"> <div class="ant-message-notice" :class="[type=='success'?'ant-message-success':'', type=='error'?'ant-message-error':'', type=='info'?'ant-message-info':'', type=='warning'?'ant-message-warning':'']"> <span>{{message}}</span> </div> </div></div></code></pre><p>2、同样在<code>/src/App.vue</code>文件中,在<style\>中增加样式:       //不能加scoped </style\></p><pre><code>.ant-message{ position: fixed; z-index: -1; width:100%; top: 51px; } .ant-content{ font-family: "Helvetica Neue For Number", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; color: rgba(0, 0, 0, 0.65); -webkit-box-sizing: border-box; box-sizing: border-box; pointer-events: none; display: inline-block; } .ant-message-notice{ padding:16px 30px; font-size: 16px; color:black; border-radius: 4px; box-shadow:0 4px 12px rgba(0,0,0,0.15); background: white; display: none; pointer-events: all; box-sizing:border-box; } .ant-message-error:before{ content:"\f057"; font-family: FontAwesome; font-style: normal; font-weight: normal; font-size: 18px; margin-right:5px; z-index: 3; color:red; } .ant-message-info:before{ content:"\f05a"; font-family: FontAwesome; font-style: normal; font-weight: normal; font-size: 18px; margin-right:5px; z-index: 3; color: #4d7dff; } .ant-message-warning:before{ content:"\f071"; font-family: FontAwesome; font-style: normal; font-weight: normal; font-size: 18px; margin-right:5px; z-index: 3; color: #ffdd65; } .ant-message-success:before{ content:"\f058"; font-family: FontAwesome; font-style: normal; font-weight: normal; font-size: 18px; margin-right:5px; z-index: 3; color: #3da638; }</code></pre><p>3、 在src/main.js中增加全局函数($message): </p><pre><code>Vue.prototype.$message = function(option){ $('.ant-message-notice span').html(option.text) $('.ant-message-notice').addClass('ant-message-'+option.type) $('.ant-message').css('z-index',9999) $ ('.ant-message-notice').show ().delay (3000).fadeOut (function () { $('.ant-message').css('z-index',-1) });}</code></pre><p>4、在需要弹出消息提示的时候使用<code>this.$message({"type": "warning", "text": "message"})</code>即可</p>]]></content>
<summary type="html">
<p>&emsp;&emsp;这里总结了一个简单的实现全局消息通知的方法,实现的效果如下图:<br><img src="/upload/global_message_notice.png" alt="imgName"><br>1、修改<code>/src/App.vue</cod
</summary>
<category term="vue.js" scheme="http://yoursite.com/categories/vue-js/"/>
<category term="vue.js" scheme="http://yoursite.com/tags/vue-js/"/>
<category term="消息组件" scheme="http://yoursite.com/tags/%E6%B6%88%E6%81%AF%E7%BB%84%E4%BB%B6/"/>
</entry>
<entry>
<title>vue.js学习总结(2)</title>
<link href="http://yoursite.com/2018/06/28/vue.js%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%EF%BC%882%EF%BC%89/"/>
<id>http://yoursite.com/2018/06/28/vue.js学习总结(2)/</id>
<published>2018-06-28T02:20:46.000Z</published>
<updated>2018-06-28T02:22:42.874Z</updated>
<content type="html"><![CDATA[<p>  上一篇文章介绍了vue.js如何快速创建一个应用,以及相关的目录介绍,编译部署等问题。这一篇文章主要总结一些项目通用的用法。 </p><h4 id="1-添加jquery和Bootstrap支持"><a href="#1-添加jquery和Bootstrap支持" class="headerlink" title="1.添加jquery和Bootstrap支持"></a>1.添加jquery和Bootstrap支持</h4><p>  1)在项目根路径下安装相应模块:<br>    <code>cnpm install bootstrap jquery –save</code><br>  2)增加jquery支持:<br>    <code>//file:webpack.base.conf.js</code><br>    头部增加 const webpack = require(‘webpack’)<br>    module.exports模块里面增加: </p><pre><code>plugins: [ new webpack.ProvidePlugin({ $: "jquery", jQuery: "jquery", "windows.jQuery": "jquery" })],</code></pre><p>    最后在main.js中加入import $ from ‘jquery’,完成jquery的引入</p><p>  3)增加bootstrap支持:<br>    在入口文件main.js中加入:<br>      <code>import './assets/css/bootstrap.min.css'</code><br>      <code>import './assets/js/bootstrap.min'</code><br>    在assets文件中新增css、js、fonts文件夹,分别在里面放入指定的bootstrap文件 </p><h4 id="2-增加fontAwesome图标支持:"><a href="#2-增加fontAwesome图标支持:" class="headerlink" title="2.增加fontAwesome图标支持:"></a>2.增加fontAwesome图标支持:</h4><p>  方案一: 在index.html中合适的地方增加: </p><pre><code><link rel="stylesheet" href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.css"> </code></pre><p>  方案二: 在<a href="http://fontawesome.dashgame.com/" target="_blank" rel="noopener">Font Awesome官网</a>下载资源包,将解压后的文件依次放入<code>/src/assets/</code>下的对应文件夹(没有则新建)中,然后在main.js中导入指定文件:</p><pre><code>import './assets/css/font-awesome.min.css' </code></pre><h4 id="3-使用js加密工具jsencript"><a href="#3-使用js加密工具jsencript" class="headerlink" title="3.使用js加密工具jsencript:"></a>3.使用js加密工具jsencript:</h4><p>  安装jsencript模块:<br>    <code>cnpm install jsencrypt --save</code><br>  在需要的模块中使用:<br>    <code>import JSEncrypt from 'jsencrypt'</code> </p><h4 id="4-根据环境设置不同变量值"><a href="#4-根据环境设置不同变量值" class="headerlink" title="4.根据环境设置不同变量值"></a>4.根据环境设置不同变量值</h4><p>  对于开发/生产环境(development/production)设置不同的变量值,代码如下: </p><pre><code>var runenv = process.env.NODE_ENVvar backend_url = runenv=="development"?"http://localhost:8000/":"/" </code></pre><h4 id="5-设置通用函数"><a href="#5-设置通用函数" class="headerlink" title="5.设置通用函数"></a>5.设置通用函数</h4><p>  main.js里面设置,组件中引用方式为:<code>this.getQueryVariable("xxx")</code></p><pre><code>//获取get请求查询参数Vue.prototype.getQueryVariable = function (variable) { var query = window.location.search.substring(1); var vars = query.split("&"); for (var i=0;i<vars.length;i++) { var pair = vars[i].split("="); if(pair[0] == variable){return pair[1];} } return(false); }//判断对象是否为空Vue.prototype.checkNone = function(obj){ if(obj == '' || obj == undefined || obj == null || obj == false){ return false }else{return true} }//获取cookie值:Vue.prototype.getCookie = function(name){ var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); return r ? r[1] : undefined; } </code></pre><h4 id="6-路由配置"><a href="#6-路由配置" class="headerlink" title="6.路由配置"></a>6.路由配置</h4><p>  1)导入组件 import componentName from ‘@/components/x/y/…’<br>  2)如果去掉url中带入的#号,则设置mode : ‘history’; base : ‘/baseUri/‘<br>  3)增加routes列表项,{path:’/abc’,name:’abc’,component : componentName} </p><h4 id="7-路由匹配"><a href="#7-路由匹配" class="headerlink" title="7.路由匹配"></a>7.路由匹配</h4><p>  若访问的uri不在路由配置中,则路由到404页面。设置/src/App.vue文件,增加mounted()函数如下:</p><pre><code>mounted(){ if(!this.$route.matched || this.$route.matched.length === 0){ window.location.href = this.getBackendUrl()+'errorpage/404/404.html?path='+this.$route.path }}</code></pre><h4 id="8-拖拽排序"><a href="#8-拖拽排序" class="headerlink" title="8.拖拽排序"></a>8.拖拽排序</h4><p>  a) 项目路径下执行<code>npm install awe-dnd --save</code><br>  b) main.js中引入: </p><pre><code>import VueDND from 'awe-dnd'Vue.use(VueDND)</code></pre><p>  c) template中引用: </p><pre><code><div class="header-list"><div class="header-item> v-for="requestHeader in requestHeaders" v-dragging="{ item: requestHeader, list: requestHeaders, group: 'requestHeader' }" :key="requestHeader.id" :id="'requestHeader'+requestHeader.id"> ....内容...</div></div></code></pre><p>  d) requestHeaders内容: </p><pre><code>[{"id":1,"value":"x"},{"id":2,"value":"y"},...]</code></pre><p>  e) 添加事件: </p><pre><code>export default { mounted () { this.$dragging.$on('dragged', ({ value }) => { console.log(value.item) console.log(value.list) console.log(value.otherData) }) this.$dragging.$on('dragend', () => { }) }}</code></pre><p>  附录:<br>    设定行内部分元素拖拽的方法:<br>    1)给需要拖拽的元素增加属性:draggable=”true”,并修改光标style=”cursor:move;”<br>    2)修改awe-dnd源码,在文件vue-dragging.es5.js和vue-dragging.js中,注释掉这一行:el.setAttribute(‘draggable’, ‘true’);</p><h4 id="9-安装调试工具"><a href="#9-安装调试工具" class="headerlink" title="9.安装调试工具"></a>9.安装调试工具</h4><p>  a) 全局安装<code>cnpm install -g @vue/devtools</code><br>  b) 运行:<code>vue-devtools</code><br>  c) 在应用的<head>中增加:(发布的时候应该移除)<br>    <code><script src="http://localhost:8098"></script></code><br>    如果是APP或远程机上执行的应用,则: </head></p><pre><code><script> window.__VUE_DEVTOOLS_HOST__ = '<your-local-ip>' // default: localhost window.__VUE_DEVTOOLS_PORT__ = '<devtools-port>' // default: 8098</script><script src="http://<your-local-ip>:8098"></script></code></pre><p>  d) <code>npm run dev</code> 运行应用,devtools会自动连接</p>]]></content>
<summary type="html">
<p>&emsp;&emsp;上一篇文章介绍了vue.js如何快速创建一个应用,以及相关的目录介绍,编译部署等问题。这一篇文章主要总结一些项目通用的用法。 </p>
<h4 id="1-添加jquery和Bootstrap支持"><a href="#1-添加jquery和Boo
</summary>
<category term="vue.js" scheme="http://yoursite.com/categories/vue-js/"/>
<category term="vue.js" scheme="http://yoursite.com/tags/vue-js/"/>
<category term="前端" scheme="http://yoursite.com/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>vue.js学习总结(1)</title>
<link href="http://yoursite.com/2018/06/26/vue.js%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%EF%BC%881%EF%BC%89/"/>
<id>http://yoursite.com/2018/06/26/vue.js学习总结(1)/</id>
<published>2018-06-26T02:46:51.000Z</published>
<updated>2018-06-26T11:32:39.176Z</updated>
<content type="html"><![CDATA[<p>  提到Vue.js,都会提到MVVM架构,那么什么是MVVM呢?MVVM可以拆分成:View — ViewModel — Model三部分,如下图:<br><img src="/upload/mvvm.jpg" alt="imgName"><br>那么我们怎么理解MVVM呢?<br>上图中,左侧的View相当于我们的DOM内容,我们所看到的页面视图,右侧的Model相当于我们的数据对象,比如一个对象的信息: </p><blockquote><p>{<br>name:”张三”,<br>age:21,<br>} </p></blockquote><p>而中间的监控者就负责监控两侧的数据,并相对应地通知另一侧进行修改。比如:你在Model层中修改了name的值为:“李四”,那么View视图层显示的“张三”也会自动变成了“李四”,而这个过程就是有ViewModel来操作的,不需要你手动地去写代码去实现(你不用再手动操作DOM了)。</p><p>那么如何快速创建一个vue.js应用呢? </p><h3 id="1-创建应用"><a href="#1-创建应用" class="headerlink" title="1.创建应用"></a>1.创建应用</h3><p>  使用<code>vue-cli</code>工具配合webpack构建工具创建,依次执行如下命令: </p><pre><code>//安装vue-cli (若未安装vue-cli) cnpm install --global vue-cli//创建应用myproject vue init webpack myproject//安装所需node依赖包 cd myproject npm init</code></pre><h3 id="2-运行应用-dev环境"><a href="#2-运行应用-dev环境" class="headerlink" title="2.运行应用(dev环境)"></a>2.运行应用(dev环境)</h3><p>  在项目目录下,执行命令<code>npm run dev</code>,直到控制台显示<code>Your application is running here: http://localhost:8080</code>,表示应用创建成功,访问<a href="http://localhost:8080" target="_blank" rel="noopener">http://localhost:8080</a>即可查看。 </p><h3 id="3-目录介绍"><a href="#3-目录介绍" class="headerlink" title="3. 目录介绍"></a>3. 目录介绍</h3><pre><code>projectName |--build |--build.js(生产环境构建) / webpack.back.conf.js(配置webpack,增加Jquery) / webpack.dev.conf.js(添加模拟API返回数据) |--config |--index.js / dev.env.js / prod.env.js / test.env.js (index.js配置静态路径相对位置,绑定主机指定IP、端口,关闭Eslint检测useEslint: false,) |--src |--assets |--css / fonts / image / js (静态文件目录) |--components |--自建vue文件 (由<template><script><style>三部分组成) |--router |--index.js (路由配置,路由模式,history模式去掉url中的#号) |--App.vue (根组件,配置router-view,根据路由不同展示不同组件) |--main.js (引入vue框架,定义vue实例,绑定vue实例(#el)到index.html的DOM元素中去,引入根组件,引入路由) |--index.html (主页内容)</code></pre><h3 id="4-应用编译"><a href="#4-应用编译" class="headerlink" title="4. 应用编译"></a>4. 应用编译</h3><p>  在项目目录下,执行命令<code>npm run build</code>,编译完成后,在应用目录下生成<code>dist</code>文件夹,部署时只用到该文件夹即可。<br>  默认生成的<code>index.html</code>在<code>dist</code>根目录下,若使用Nginx部署应用,则只需要配置如下:<br><pre>location ~<em> / {<br> root /path/to/dist; // 将/path/to/dist修改为dist实际路径即可<br> index login.html index.html;<br> try_files $uri $uri/ /myproject/;<br>}</em></pre><br>若需要将站点访问的uri加上一个统一前缀,比如加上<code>/myproject</code>后,<code><a href="http://localhost:8080/login.html" target="_blank" rel="noopener">http://localhost:8080/login.html</a></code>变成<code><a href="http://localhost:8080/myproject/login.html" target="_blank" rel="noopener">http://localhost:8080/myproject/login.html</a></code>,此时如果直接访问则会提404,那么该如何处理呢?<br>  最开始想到的办法是,修改<code>nginx.conf</code>,将<code>location ~ /</code>修改为<code>location ~* /myproject/</code>,结果发现实际请求的本地路径前面都会加上/myproject,所以这种方式不行。后来通过实践,要实现这种要求,可以有两种方式:<br>  a) 在dist目录下新增一个目录myproject,并将<code>index.html</code>移动到这个目录下,这个时候访问就正常了。<br>  b) 修改项目路径下<code>/config/index.js</code>文件,修改<code>build:index</code>里面的<code>../dist/index.html</code>为<code>../dist/project/index.html</code></p>]]></content>
<summary type="html">
<p>&emsp;&emsp;提到Vue.js,都会提到MVVM架构,那么什么是MVVM呢?MVVM可以拆分成:View — ViewModel — Model三部分,如下图:<br><img src="/upload/mvvm.jpg" alt="imgName"><br>那么
</summary>
<category term="vue.js" scheme="http://yoursite.com/categories/vue-js/"/>
<category term="vue.js" scheme="http://yoursite.com/tags/vue-js/"/>
<category term="前端" scheme="http://yoursite.com/tags/%E5%89%8D%E7%AB%AF/"/>
</entry>
<entry>
<title>python使用微服务之consul</title>
<link href="http://yoursite.com/2018/06/25/python%E4%BD%BF%E7%94%A8%E5%BE%AE%E6%9C%8D%E5%8A%A1%E4%B9%8Bconsul/"/>
<id>http://yoursite.com/2018/06/25/python使用微服务之consul/</id>
<published>2018-06-25T12:04:24.000Z</published>
<updated>2018-06-26T01:58:16.240Z</updated>
<content type="html"><![CDATA[<blockquote><p>安装python-consul模块:<code>pip install python-consul</code> </p></blockquote><h4 id="1-注册服务"><a href="#1-注册服务" class="headerlink" title="1. 注册服务"></a>1. 注册服务</h4><p>  使用<code>/v1/agent/service/register</code>这个HTTP APi来进行注册,注册的目标consul主机,可以是server,也可以是client,端口是8500。如果非本地主机注册,则需要目标主机启动consul时加上<code>-client 目标访问ip</code>,否则无法远程访问。 代码如下: </p><pre><code>#coding=utf-8import urllib2,jsonregister_url = "http://%s:8500/v1/agent/service/register"%"localhost"register_data = { "id": "serviceID", #服务ID,每个datacenter中唯一 "name": "serviceName", #服务名称,服务表示 "address": "10.x.x.x", #服务提供者的IP "port": 8080, #服务提供者的端口 "tags": ["dev"], "checks": [{"http": "http://localhost:8080/check","interval": "5s"}] }request = urllib2.Request(register_url,data=json.dumps(register_data))request.add_header('Content-Type', 'application/json')request.get_method = lambda: 'PUT'urllib2.urlopen(request)</code></pre><h4 id="2-删除服务"><a href="#2-删除服务" class="headerlink" title="2. 删除服务"></a>2. 删除服务</h4><p>  使用<code>/v1/agent/service/deregister/[serviceID]</code>这个HTTP API来删除服务。删除和注册必须要在同一台consul主机上进行,不能通过A主机注册,又通过B主机删除。但发起注册和发起删除服务的主机可以不同。代码如下:</p><pre><code># coding=utf-8import urllib2deregister_service_url = 'http://%s:8500/v1/agent/service/deregister/serviceID'%"localhost"request = urllib2.Request(deregister_service_url)request.add_header('Content-Type', 'application/json')request.get_method = lambda: 'PUT'urllib2.urlopen(request)</code></pre><h4 id="3-获取Consul服务列表"><a href="#3-获取Consul服务列表" class="headerlink" title="3.获取Consul服务列表"></a>3.获取Consul服务列表</h4><p>  consul中没有注册中心的概念,一般是通过consul client进行服务注册和服务发现,这台用来服务注册和服务发现的主机就是一个Agent,生成一个Agent的代码为<code>agent = consul.Consul(host='172.30.1.71', port="8500")</code>。列出某个agent上可供访问到的所有服务(即当前agent所在datacenter中的所有服务)的代码如下:</p><pre><code># coding=utf-8from consul import Consuldef getAllService(agent): #agent = Consul(host='172.30.1.71', port="8500") newestIndex,services = agent.catalog.services() #('23452', {u'test': [u'dev'], u'consul': [], u'mockPlatform': []}) ==>{'服务名1':[tag1,tag2,...],...} servicesList = services.keys() servicesList.remove('consul') #排除掉名称为'consul'的服务,为server端自带服务 return servicesList</code></pre><h4 id="4-获取服务"><a href="#4-获取服务" class="headerlink" title="4. 获取服务"></a>4. 获取服务</h4><p>  查找指定服务的服务提供者,并返回访问速度最快的主机。代码如下:</p><pre><code># coding=utf-8from consul import Consulimport re,requestsdef getConnectTime(url): return requests.head(url).elapsed.total_seconds()def getService(agent,name): # 负载均衡获取服务实例agent = Consul(host='172.30.1.71', port="8500") newestIndex, nodeList = agent.catalog.service(name) if not nodeList: raise Exception('There is no service: [%s] can be used!'%name) dcset = set() # DataCenter 集合 初始化 for service in nodeList: dcset.add(service.get('Datacenter')) serviceList = [] # 服务列表 初始化 for dc in dcset: newestIndex,healthNodeList =agent.health.service(name,dc=dc,passing=True) for serv in healthNodeList: node = serv.get('Node').get("Node") healthNodeList = agent.health.checks(name)[1] for i in healthNodeList: if i.get('Node')!= node: continue else: health_output = i.get('Output') health = re.search(r'HTTP GET (http:.*): 2.*',health_output).group(1) address = serv.get('Service').get('Address') port = serv.get('Service').get('Port') serviceList.append({'address': address,'port': port,'health':health}) if len(serviceList) == 0: raise Exception('no serveice can be used') else: ret = () fastest = None for s in serviceList: health = s.get('health') http_conn_time = getConnectTime(health) if not fastest: fastest = http_conn_time ret = (s['address'], int(s['port'])) else: if http_conn_time<fastest: ret = (s['address'], int(s['port'])) return ret</code></pre><h4 id="5-consul提供的所有HTTP-API"><a href="#5-consul提供的所有HTTP-API" class="headerlink" title="5.consul提供的所有HTTP API"></a>5.consul提供的所有HTTP API</h4><p>  所有的endpoints主要分为以下类别:<br>    <strong>kv</strong> - Key/Value存储<br>    <strong>agent</strong> - Agent控制<br>    <strong>catalog</strong> - 管理nodes和services<br>    <strong>health</strong> - 管理健康监测<br>    <strong>session</strong> - Session操作<br>    <strong>acl</strong> - ACL创建和管理event - 用户Events<br>    <strong>status</strong> - Consul系统状态 </p><pre><code>agent endpoints: (agent endpoints用来和本地agent进行交互,一般用来服务注册和检查注册,支持以下接口) /v1/agent/checks : 返回本地agent注册的所有检查(包括配置文件和HTTP接口) /v1/agent/services : 返回【本地agent注册】的所有 服务。注意!!只能是本地注册的服务 /v1/agent/members : 返回agent在集群的gossip pool中看到的成员 /v1/agent/self : 返回本地agent的配置和成员信息 /v1/agent/join/<address> : 触发本地agent加入node /v1/agent/force-leave/<node>: 强制删除node /v1/agent/check/register : 在本地agent增加一个检查项,使用PUT方法传输一个json格式的数据 /v1/agent/check/deregister/<checkID> : 注销一个本地agent的检查项 /v1/agent/check/pass/<checkID> : 设置一个本地检查项的状态为passing /v1/agent/check/warn/<checkID> : 设置一个本地检查项的状态为warning /v1/agent/check/fail/<checkID> : 设置一个本地检查项的状态为critical /v1/agent/service/register : 在本地agent增加一个新的服务项,使用PUT方法传输一个json格式的数据 /v1/agent/service/deregister/<serviceID> : 注销一个本地agent的服务项,用PUT请求Consul 的这个deregister接口,附上服务id就可以成功注销掉服务了(注意是服务实例的id,不是服务名catalog endpoints: (catalog endpoints用来注册/注销nodes、services、checks) /v1/catalog/register : Registers a new node, service, or check /v1/catalog/deregister : Deregisters a node, service, or check /v1/catalog/datacenters : Lists known datacenters /v1/catalog/nodes : Lists nodes in a given DC /v1/catalog/services : Lists services in a given DC /v1/catalog/service/<service> : Lists the nodes in a given service /v1/catalog/node/<node> : Lists the services provided by a nodehealth endpoints: (health endpoints用来查询健康状况相关信息,该功能从catalog中单独分离出来) /v1/healt/node/<node>: 返回node所定义的检查,可用参数dc= /v1/health/checks/<service>: 返回和服务相关联的检查,可用参数dc= /v1/health/service/<service>: 返回给定datacenter中给定node中service /v1/health/state/<state>: 返回给定datacenter中指定状态的服务,state可以是"any", "unknown", "passing", "warning", or "critical",可用参数dc=session endpoints: (session endpoints用来create、update、destory、query sessions) /v1/session/create: Creates a new session /v1/session/destroy/<session>: Destroys a given session /v1/session/info/<session>: Queries a given session /v1/session/node/<node>: Lists sessions belonging to a node /v1/session/list: Lists all the active sessionsacl endpoints: (acl endpoints用来create、update、destory、query acl) /v1/acl/create: Creates a new token with policy /v1/acl/update: Update the policy of a token /v1/acl/destroy/<id>: Destroys a given token /v1/acl/info/<id>: Queries the policy of a given token /v1/acl/clone/<id>: Creates a new token by cloning an existing token /v1/acl/list: Lists all the active tokensstatus endpoints: (status endpoints用来或者consul 集群的信息) /v1/status/leader : 返回当前集群的Raft leader /v1/status/peers : 返回当前集群中同事 </code></pre>]]></content>
<summary type="html">
<blockquote>
<p>安装python-consul模块:<code>pip install python-consul</code> </p>
</blockquote>
<h4 id="1-注册服务"><a href="#1-注册服务" class="header
</summary>
<category term="python" scheme="http://yoursite.com/categories/python/"/>
<category term="consul" scheme="http://yoursite.com/categories/python/consul/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="consul" scheme="http://yoursite.com/tags/consul/"/>
</entry>
<entry>
<title>python多进程编程</title>
<link href="http://yoursite.com/2018/01/10/python%E5%A4%9A%E8%BF%9B%E7%A8%8B%E7%BC%96%E7%A8%8B/"/>
<id>http://yoursite.com/2018/01/10/python多进程编程/</id>
<published>2018-01-10T01:24:47.000Z</published>
<updated>2018-06-25T11:25:30.000Z</updated>
<content type="html"><![CDATA[<p>  Python由于全局锁GIL的存在,无法享受多线程带来的性能提升。multiprocessing包采用子进程的技术避开了GIL,使用multiprocessing可以进行多进程编程提高程序效率。 </p><h2 id="multiprocessing-Process对象"><a href="#multiprocessing-Process对象" class="headerlink" title="multiprocessing.Process对象"></a>multiprocessing.Process对象</h2><pre><code>from multiprocessing import Process#定义子进程处理函数def worker(a,b,c=3,d=6): passp = Process(target=worker,name='run_worker',args=(1,2),kwargs={d:'4'})p.daemon = True #设置子进程是否随父进程终止而自动终止,一定要在start方法调用之前设置p.start()p.join()</code></pre><p>  Process对象的初始化参数为<span style="background:lightgrey">Process(group=None, target=None, name=None, args=(), kwargs={})</span>,其中group参数必须为None(为了与threading.Thread的兼容),target指向可调用对象(该对象在新的子进程中运行),name是为该子进程命的名字(默认是Proess-1,Process-2, …这样),args是被调用对象的位置参数的元组列表,kwargs是被调用对象的关键字参数。</p><p>  子进程终结时会通知父进程并清空自己所占据的内存,在内核里留下退出信息(exit code,如果顺利运行,为0;如果有错误或异常状况,为大于零的整数)。父进程得知子进程终结后,需要对子进程使用wait系统调用,wait函数会从内核中取出子进程的退出信息,并清空该信息在内核中占据的空间。</p><p>  如果父进程早于子进程终结,子进程变成孤儿进程,孤儿进程会被过继给init进程,init进程就成了该子进程的父进程,由init进程负责该子进程终结时调用wait函数。如果父进程不对子进程调用wait函数,子进程成为僵尸进程。僵尸进程积累时,会消耗大量内存空间。 可以设置子进程的daemon属性为True,则父进程终结时,自动终止该子进程。</p><p>  如果在父进程中不调用子进程的<code>p.join()</code>方法,则主进程与父进程并行工作。join方法的作用主要是(以下线程均可换为子进程): </p><ul><li>阻塞主进程(挡住,无法执行join以后的语句),专注执行多线程。 </li><li>多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。 </li><li>无参数时,则等待到该线程结束,才开始执行下一个线程的join。 </li><li>设置参数后,则等待该线程这么长时间如果没有执行完就阻塞该线程。 <blockquote><h3 id="将进程定义为类"><a href="#将进程定义为类" class="headerlink" title="将进程定义为类"></a>将进程定义为类</h3><pre><code>import multiprocessingimport timeclass ClockProcess(multiprocessing.Process): def __init__(self, interval): multiprocessing.Process.__init__(self) self.interval = interval def run(self): n = 5 while n > 0: print("the time is {0}".format(time.ctime())) time.sleep(self.interval) n -= 1 if __name__ == '__main__': p = ClockProcess(3) p.start()</code></pre></blockquote></li></ul><h2 id="multiprocessing-Queue对象"><a href="#multiprocessing-Queue对象" class="headerlink" title="multiprocessing.Queue对象"></a>multiprocessing.Queue对象</h2><p>使用Queue对象可以实现进程间通信,并且Queue对象是线程及进程安全的:</p><pre><code>from multiprocessing import Queue, Processdef worker(q): q.put(['abc',123,'x'])if __name__ == "__main__": q = Queue() p = Process(target = worker,args=(q,)) p.daemon = True p.start() p.join() print q.get()</code></pre><h2 id="multiprocessing-Pipe对象"><a href="#multiprocessing-Pipe对象" class="headerlink" title="multiprocessing.Pipe对象"></a>multiprocessing.Pipe对象</h2><p>Pipe对象返回的元组分别代表管道的两端p[0]和p[1],管道默认是全双工,两端都支持send和recv方法,两个进程分别操作管道两端时不会有冲突,两个进程对管道一端同时读写时可能会有冲突:</p><pre><code>from multiprocessing import Pipe, Processdef worker(p): p.send('hello,world!')if __name__ == "__main__": left,right = Pipe() p = Process(target=worker,args=(left,)) p.daemon = True p.start() p.join() print right.recv()</code></pre><p>如果声明了p = Pipe(duplex=False)的单向管道,则p[0]只负责接受消息,p[1]只负责发送消息。 </p><h2 id="multiprocessing-Lock对象"><a href="#multiprocessing-Lock对象" class="headerlink" title="multiprocessing.Lock对象"></a>multiprocessing.Lock对象</h2><p>当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。<br>Lock有两种方法使用:1.使用with上下文管理器;2.使用acquire()和release()方法 </p><pre><code>import multiprocessingimport sysdef worker_with(lock, f): with lock: fs = open(f, 'a+') n = 10 while n > 1: fs.write("Lockd acquired via with\n") n -= 1 fs.close()def worker_no_with(lock, f): lock.acquire() try: fs = open(f, 'a+') n = 10 while n > 1: fs.write("Lock acquired directly\n") n -= 1 fs.close() finally: lock.release()if __name__ == "__main__": lock = multiprocessing.Lock() f = "file.txt" w = multiprocessing.Process(target = worker_with, args=(lock, f)) nw = multiprocessing.Process(target = worker_no_with, args=(lock, f)) w.start() nw.start() print "end"</code></pre><h2 id="multiprocessing-Value-和-multiprocessing-Array对象"><a href="#multiprocessing-Value-和-multiprocessing-Array对象" class="headerlink" title="multiprocessing.Value 和 multiprocessing.Array对象"></a>multiprocessing.Value 和 multiprocessing.Array对象</h2><p>在进程间共享状态可以使用multiprocessing.Value和multiprocessing.Array这样特殊的共享内存对象:<br>  <code>Value(typecode_to_type , obj)</code><br>  <code>Array(typecode_to_type , int | list | tuple... , lock=True)</code><br>Array的第二个参数传入为int类型的一个数的时候,会初始化值为0长度为这个值的数组</p><pre><code>typecode_to_type = { 'c': ctypes.c_char, 'b': ctypes.c_byte, 'B': ctypes.c_ubyte, 'h': ctypes.c_short, 'H': ctypes.c_ushort, 'i': ctypes.c_int, 'I': ctypes.c_uint, 'l': ctypes.c_long, 'L': ctypes.c_ulong, 'f': ctypes.c_float, 'd': ctypes.c_double }</code></pre><p>读写Value数据用属性v.value,读写Array数据用切片运算a[i]</p><pre><code>from multiprocessing import Pipe, Process,Value,Arraydef worker(v,a): v.value = 1.2 for i in range(10): a[i] = -iif __name__ == "__main__": v = Value('f',0.0) a = Array('i',range(10)) print v.value print a[3] p = Process(target=worker,args=(v,a)) p.daemon = True p.start() p.join() print v.value print a[3]</code></pre><h2 id="multiprocessing-Manager对象"><a href="#multiprocessing-Manager对象" class="headerlink" title="multiprocessing.Manager对象"></a>multiprocessing.Manager对象</h2><p>multiprocessing.Manager对象创建一个服务进程,像是一个保存状态的代理,其他进程通过与代理的接口通信取得状态信息,服务进程支持更多的数据类型,使用起来比共享内存更灵活。</p><pre><code>from multiprocessing import Process, Managerdef func(d, l): d['1'] = 2 d[2] = 'str' d[3.0] = None for i in range(len(l)): l[i] = -iif __name__ == "__main__": m = Manager() l = m.list(range(10)) d = m.dict() p = Process(target=func, args=(d, l,)) p.start() p.join() print d print l</code></pre><h2 id="multiprocessing-Pool对象"><a href="#multiprocessing-Pool对象" class="headerlink" title="multiprocessing.Pool对象"></a>multiprocessing.Pool对象</h2><p>  在利用Python进行系统管理的时候,特别是同时操作多个文件目录,或者远程控制多台主机,并行操作可以节约大量的时间。当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。<br>  Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行它。</p><pre><code>#coding: utf-8import multiprocessingimport timedef func(msg): print "msg:", msg time.sleep(3) print "end"if __name__ == "__main__": pool = multiprocessing.Pool(processes = 3) for i in xrange(4): msg = "hello %d" %(i) pool.apply_async(func, (msg, )) #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去 print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~" pool.close() pool.join() #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束 print "Sub-process(es) done." </code></pre><p>函数解释: </p><ul><li>apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞的(理解区别,看例1例2结果区别) </li><li>close() 关闭pool,使其不在接受新的任务 </li><li>terminate() 结束工作进程,不在处理未完成的任务。 </li><li>join() 主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。 </li></ul><p>执行说明:<br>  创建一个进程池pool,并设定进程的数量为3,xrange(4)会相继产生四个对象[0, 1, 2, 4],四个对象被提交到pool中,因pool指定进程数为3,所以0、1、2会直接送到进程中执行,当其中一个执行完事后才空出一个进程处理对象3,所以会出现输出“msg: hello 3”出现在”end”后。因为为非阻塞,主函数会自己执行自个的,不搭理进程的执行,所以运行完for循环后直接输出“mMsg: hark~ Mark~ Mark<del>~</del><del>~</del><del>~</del><del>~</del>~~”,主程序在pool.join()处等待各个进程的结束。</p><h2 id="multiprocessing-Semaphore对象"><a href="#multiprocessing-Semaphore对象" class="headerlink" title="multiprocessing.Semaphore对象"></a>multiprocessing.Semaphore对象</h2><p>Semaphore用来控制对共享资源的访问数量,例如池的最大连接数。当连接数量达到设定值后,只有当旧的连接释放掉,才会为资源创建新连接。 </p><pre><code>import multiprocessingimport timedef worker(s, i): s.acquire() print(multiprocessing.current_process().name + "acquire"); time.sleep(i) print(multiprocessing.current_process().name + "release\n"); s.release()if __name__ == "__main__": s = multiprocessing.Semaphore(2) for i in range(5): p = multiprocessing.Process(target = worker, args=(s, i*2)) p.start()</code></pre><h2 id="multiprocessing-Event对象"><a href="#multiprocessing-Event对象" class="headerlink" title="multiprocessing.Event对象"></a>multiprocessing.Event对象</h2><p>Event用来实现进程间同步通信。</p><pre><code>import multiprocessingimport timedef wait_for_event(e): print("wait_for_event: starting") e.wait() print("wairt_for_event: e.is_set()->" + str(e.is_set()))def wait_for_event_timeout(e, t): print("wait_for_event_timeout:starting") e.wait(t) print("wait_for_event_timeout:e.is_set->" + str(e.is_set()))if __name__ == "__main__": e = multiprocessing.Event() w1 = multiprocessing.Process(name = "block", target = wait_for_event, args = (e,)) w2 = multiprocessing.Process(name = "non-block", target = wait_for_event_timeout, args = (e, 2)) w1.start() w2.start() time.sleep(3) e.set() print("main: event is set")</code></pre>]]></content>
<summary type="html">
<p>&emsp;&emsp;Python由于全局锁GIL的存在,无法享受多线程带来的性能提升。multiprocessing包采用子进程的技术避开了GIL,使用multiprocessing可以进行多进程编程提高程序效率。 </p>
<h2 id="multiprocessi
</summary>
<category term="python" scheme="http://yoursite.com/categories/python/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="multiprocessing" scheme="http://yoursite.com/tags/multiprocessing/"/>
</entry>
<entry>
<title>RF关联案例设置方法</title>
<link href="http://yoursite.com/2018/01/08/RF%E5%85%B3%E8%81%94%E6%A1%88%E4%BE%8B%E8%AE%BE%E7%BD%AE%E6%96%B9%E6%B3%95/"/>
<id>http://yoursite.com/2018/01/08/RF关联案例设置方法/</id>
<published>2018-01-08T02:28:40.000Z</published>
<updated>2018-06-25T11:25:30.000Z</updated>
<content type="html"><![CDATA[<p>  虽然手工测试用例的编写规则一般建议,用例之间要尽量避免关联性,测试点要尽量独立,但如果在自动化领域,考虑到自动化的执行效率,以及自动化数据流之间的关联性,这种建议往往比较难以实现。比如说,测试一个用户登录之后的某些功能,必须要先在环境中注册好一个用户,在自动化中可实现的方法有两种:<br>  1. 将已注册的用户设置成全局变量放入到配置文件中,每次新环境执行自动化用例前,手工生成一个已注册用户,再将该用户信息写入配置文件中去;<br>  2. 单独写一条自动化用例实现注册功能,其它测试用例基于该注册用例产生的注册用户进一步执行;<br>  方法1执行自动化前需要手工去准备数据,这里只列举出了一种场景,假如测试场景中有更多前置条件需要设置,则需要手工生成的数据也会很多,这种方法无疑是效率很低的一种半自动化;方法2也存在一种问题,在手工测试,如果测试人员要测试更多的已登录功能,一般都会先将注册更能调试通,能注册成功之后再会去测试其它功能,假如注册都失败了,后面的用例也不需要再执行了;但在自动化执行过程中,注册失败了,还是会继续执行后续用例,这将会导致自动化执行效率很低。<br>  本文就是基于robot framework自动化,模拟手工测试过程解决方法2中存在的问题。 </p><p><strong>1. 重复执行单条用例N次,直到成功</strong></p><blockquote><p>参照上一篇文章 <a href="https://turbolento.github.io/2018/01/08/RF%E6%89%A7%E8%A1%8C%E5%A4%B1%E8%B4%A5%E7%94%A8%E4%BE%8B%E9%87%8D%E8%B7%91/" target="_blank" rel="noopener">RF执行失败用例重跑</a> </p></blockquote><p><strong>2. 如果前置用例失败,则关联性用例直接标记失败</strong><br>2.1 自定义关键字 </p><table><thead><tr><th style="text-align:left"><span style="color:#42E205">关联案例集_案例初始化</span></th><th style="text-align:left"></th><th style="text-align:left"></th><th style="text-align:left"></th><th style="text-align:left"></th></tr></thead><tbody><tr><td style="text-align:left"><span style="color:blue">初始化环境</span></td><td style="text-align:left"></td><td style="text-align:left"></td><td style="text-align:left"></td><td style="text-align:left"></td></tr><tr><td style="text-align:left"><span style="color:blue">Run Keyword If</span></td><td style="text-align:left">‘clearSuiteStatus’ in @{TEST_TAGS}</td><td style="text-align:left"><span style="color:blue">Set Suite Variable</span></td><td style="text-align:left">${suite_status}</td><td style="text-align:left">RUN</td></tr><tr><td style="text-align:left"><span style="color:blue">Run Keyword If</span></td><td style="text-align:left">‘${suite_status}’ == ‘INTERRUPT’</td><td style="text-align:left"><span style="color:blue">Fail</span></td><td style="text-align:left">前置条件设置失败!</td><td style="text-align:left"> </td></tr></tbody></table><table><thead><tr><th style="text-align:left"><span style="color:#42E205">关联案例集_案例集初始化</span></th><th style="text-align:left"></th><th style="text-align:left"></th><th style="text-align:left"></th></tr></thead><tbody><tr><td style="text-align:left"><span style="color:blue">Set Suite Variable</span></td><td style="text-align:left">${suite_status}</td><td style="text-align:left">RUN</td><td style="text-align:left">#RUN/INTERRUPT</td></tr></tbody></table><table><thead><tr><th style="text-align:left"><span style="color:#42E205">关联案例集_案例析构</span></th><th style="text-align:left"></th><th style="text-align:left"></th><th style="text-align:left"></th><th style="text-align:left"></th></tr></thead><tbody><tr><td style="text-align:left"><span style="color:blue">Run Keyword If</span></td><td style="text-align:left">‘suite_init’ in @{TEST_TAGS} and ‘${TEST_STATUS}’ == ‘FAIL’</td><td style="text-align:left"><span style="color:blue">Set Suite Variable</span></td><td style="text-align:left">${suite_status}</td><td style="text-align:left">INTERRUPT</td></tr><tr><td style="text-align:left"><span style="color:blue">释放环境</span></td><td style="text-align:left"></td><td style="text-align:left"></td><td style="text-align:left"></td><td style="text-align:left"></td></tr></tbody></table><p>2.2 管理关联用例名称<br>  采用尾部加上-[大小写字母]的方式:大写字母表示前置用例,对应的小写字母表示想关联的用例,如下图:<br><img src="/upload/rf_1.png" alt="imgName"></p><p>2.3 管理用例标签 </p><ul><li>-[A]不依赖任何用例,只被其它用例依赖,则添加标签<code>suite_init</code>、<code>clearSuiteStatus</code></li><li>-[a]只依赖其它用例,不被其它用例依赖,则不添加标签</li><li>-[a][B]依赖其它标签,也被其它标签依赖,则只添加标签<code>suite_init</code></li></ul><p>2.4 设置suite前置条件<br><img src="/upload/rf_2.png" alt="imgName"></p><p>执行命令<code>pybot -Z 3 -t * E:\auto\00正常流程用例</code> 即可。</p>]]></content>
<summary type="html">
<p>&emsp;&emsp;虽然手工测试用例的编写规则一般建议,用例之间要尽量避免关联性,测试点要尽量独立,但如果在自动化领域,考虑到自动化的执行效率,以及自动化数据流之间的关联性,这种建议往往比较难以实现。比如说,测试一个用户登录之后的某些功能,必须要先在环境中注册好一个用户
</summary>
<category term="自动化" scheme="http://yoursite.com/categories/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
<category term="Robot Framework" scheme="http://yoursite.com/tags/Robot-Framework/"/>
</entry>
<entry>
<title>RF执行失败用例重跑</title>
<link href="http://yoursite.com/2018/01/08/RF%E6%89%A7%E8%A1%8C%E5%A4%B1%E8%B4%A5%E7%94%A8%E4%BE%8B%E9%87%8D%E8%B7%91/"/>
<id>http://yoursite.com/2018/01/08/RF执行失败用例重跑/</id>
<published>2018-01-08T01:06:34.000Z</published>
<updated>2018-06-25T11:25:30.000Z</updated>
<content type="html"><![CDATA[<p>  本文通过修改RF源代码,增加命令pybot参数–retry N,以实现执行过程中,test级别的用例失败后自动再执行N次,或直到成功为止,生成的日志和报告文件中只记录最后一次执行结果。<br><a id="more"></a><br><strong>修改代码如下:</strong> </p><h4 id="1-robot-run-py"><a href="#1-robot-run-py" class="headerlink" title="1. robot/run.py"></a>1. robot/run.py</h4><p>修改USAGE文档,增加 -Z –retry retry    Set the retry times if test failed. </p><blockquote><h1 id="Options"><a href="#Options" class="headerlink" title="Options "></a>Options </h1><p><span style="background:yellow">-Z –retry retry        Set the retry times if test failed.</span><br>-N –name name       Set the name of the top level test suite. Underscores<br>               in the name are converted to spaces. Default name is<br>               created from the name of the executed data source.<br>-D –doc documentation    Set the documentation of the top level test suite.<br>               Underscores in the documentation are converted to<br>               spaces and it may also contain simple HTML formatting<br>               (e.g. <em>bold</em> and <a href="http://url/" target="_blank" rel="noopener">http://url/</a>). </p></blockquote><p>增加导入模块 </p><pre><code>reload(sys) sys.setdefaultencoding('UTF-8')from xml.dom import minidom</code></pre><p>RobotFramework类增加make方法 </p><pre><code>def make(self,outxml): xmldoc = minidom.parse(outxml) suiteElementList = xmldoc.getElementsByTagName('suite') mySuite = [] for suiteElement in suiteElementList: if suiteElement.childNodes is not None: for element in suiteElement.childNodes: if element.nodeName == 'test': mySuite.append(suiteElement) break for suite in mySuite: testElements = {} for element in suite.childNodes: if element.nodeName == 'test': name = element.getAttribute('name') if testElements.get(name) == None: testElements.update({name:[element]}) else: testElements.get(name).append(element) for n,el in testElements.iteritems(): for i in el[0:-1]: textElement = i.nextSibling suite.removeChild(i) suite.removeChild(textElement) savefile = open(outxml,'w') root = xmldoc.documentElement root.writexml(savefile) savefile.close()</code></pre><p>修改RobotFramework类的main方法,插入self.make(settings.output)这段</p><pre><code>def main(self, datasources, **options): settings = RobotSettings(options) LOGGER.register_console_logger(**settings.console_output_config) LOGGER.info('Settings:\n%s' % unic(settings)) suite = TestSuiteBuilder(settings['SuiteNames'], settings['WarnOnSkipped']).build(*datasources) suite.configure(**settings.suite_config) if settings.pre_run_modifiers: suite.visit(ModelModifier(settings.pre_run_modifiers, settings.run_empty_suite, LOGGER)) with pyloggingconf.robot_handler_enabled(settings.log_level): result = suite.run(settings) LOGGER.info("Tests execution ended. Statistics:\n%s" % result.suite.stat_message) self.make(settings.output) #增加这一行,去掉此注释 if settings.log or settings.report or settings.xunit: writer = ResultWriter(settings.output if settings.log else result) writer.write_results(settings.get_rebot_settings()) return result.return_code</code></pre><h4 id="2-robot-conf-settings-py"><a href="#2-robot-conf-settings-py" class="headerlink" title="2. robot/conf/settings.py"></a>2. robot/conf/settings.py</h4><p>修改_cli_opts字典,增加 ‘Retry’:(‘retry’,1) </p><pre><code>'MonitorColors' : ('monitorcolors', 'AUTO'),'StdOut' : ('stdout', None),'StdErr' : ('stderr', None),'XUnitSkipNonCritical' : ('xunitskipnoncritical', False),'Retry' : ('retry',1)} #增加这一行,去掉此注释</code></pre><h4 id="3-robot-model-itemlist-py"><a href="#3-robot-model-itemlist-py" class="headerlink" title="3. robot/model/itemlist.py"></a>3. robot/model/itemlist.py</h4><p>修改visit方法如下: </p><pre><code>def visit(self, visitor): for item in self: if self.__module__ == 'robot.model.testcase' and hasattr(visitor,"_context"): testStatus = '' for i in range(0,int(visitor._settings._opts['Retry'])): if testStatus != 'PASS': if item.name in visitor._executed_tests: visitor._executed_tests.pop(item.name) item.visit(visitor) testStatus = visitor._context.variables['${PREV_TEST_STATUS}'] else: break else: item.visit(visitor)</code></pre><h4 id="4-robotide-contrib-testrunner-usages-py"><a href="#4-robotide-contrib-testrunner-usages-py" class="headerlink" title="4. robotide\contrib\testrunner\usages.py"></a>4. robotide\contrib\testrunner\usages.py</h4><p>修改USAGE文档,增加 -Z –retry retry    Set the retry times if test failed.这一段 </p><blockquote><h1 id="Options-1"><a href="#Options-1" class="headerlink" title="Options "></a>Options </h1><p><span style="background:yellow">-Z –retry retry        Set the retry times if test failed.</span><br>-N –name name       Set the name of the top level test suite. Underscores<br>               in the name are converted to spaces. Default name is<br>               created from the name of the executed data source.<br>-D –doc documentation    Set the documentation of the top level test suite.<br>               Underscores in the documentation are converted to<br>               spaces and it may also contain simple HTML formatting<br>               (e.g. <em>bold</em> and <a href="http://url/" target="_blank" rel="noopener">http://url/</a>). </p></blockquote><p>最后使用 <code>pybot -Z 3 -t E:\autotest\test</code> 即可实现失败重跑功能。</p>]]></content>
<summary type="html">
<p>&emsp;&emsp;本文通过修改RF源代码,增加命令pybot参数–retry N,以实现执行过程中,test级别的用例失败后自动再执行N次,或直到成功为止,生成的日志和报告文件中只记录最后一次执行结果。<br>
</summary>
<category term="自动化" scheme="http://yoursite.com/categories/%E8%87%AA%E5%8A%A8%E5%8C%96/"/>
<category term="Robot Framework" scheme="http://yoursite.com/tags/Robot-Framework/"/>
</entry>
<entry>
<title>markdown基本语法</title>
<link href="http://yoursite.com/2018/01/05/markdown%E5%9F%BA%E6%9C%AC%E8%AF%AD%E6%B3%95/"/>
<id>http://yoursite.com/2018/01/05/markdown基本语法/</id>
<published>2018-01-05T07:30:13.000Z</published>
<updated>2018-06-25T11:25:30.000Z</updated>
<content type="html"><![CDATA[<p>  Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。<br>  Markdown的语法简洁明了、学习容易,而且功能比纯文本更强,因此有很多人用它写博客。世界上最流行的博客平台WordPress和大型CMS如Joomla、Drupal都能很好的支持Markdown。完全采用Markdown编辑器的博客平台有Ghost和Typecho。</p><blockquote><p>软件:MarkdownPad </p></blockquote><h3 id="1-1-标题"><a href="#1-1-标题" class="headerlink" title="1.1 标题"></a>1.1 标题</h3><p>  (1) 1级到6级标题,快捷键<kbd>Ctrl + i</kbd> (i=1~6),等效为 <span style="color:green">>#..# 内容 #..#</span> 【注:#..#表示i个#号】</p><p>  (2) #与文字之间加上空格,是markdown标准语法规则</p><p>  (3) 在任意文字下一行加上任意个 = ,表示一级标题; 加 - 表示二级标题</p><h3 id="1-1-字体格式"><a href="#1-1-字体格式" class="headerlink" title="1.1 字体格式"></a>1.1 字体格式</h3><p>  (1)加粗快捷键 <kbd>Ctrl + B</kbd>, 等效为 <span style="color:green">**<strong>粗体文本</strong>**</span></p><p>  (2)斜体快捷键 <kbd>Ctrl + I</kbd>, 等效为 <span style="color:green">*<em>斜体文本</em>*</span></p><p>  (3)删除线: <span style="color:green"> ~~<del>删除我</del>~~ </span></p><h3 id="1-2-分割线"><a href="#1-2-分割线" class="headerlink" title="1.2 分割线"></a>1.2 分割线</h3><p>  快捷键 <kbd>Ctrl + R</kbd>,等效为 <span style="color:green">---,或者 ***</span> </p><hr><h3 id="1-3-换行"><a href="#1-3-换行" class="headerlink" title="1.3 换行"></a>1.3 换行</h3><p>  行末尾加上两个空格再回车</p><h3 id="1-4-脚注"><a href="#1-4-脚注" class="headerlink" title="1.4 脚注"></a>1.4 脚注</h3><p>  这是一个脚注的例子<a href="这里是脚注">^1</a></p><pre><code>这是一个脚注的例子[^1][^1]: 这里是脚注</code></pre><hr><hr><h3 id="2-1-引用"><a href="#2-1-引用" class="headerlink" title="2.1 引用"></a>2.1 引用</h3><p>  快捷键 <kbd>Ctrl + Q</kbd>,等效为 <span style="color:green">> 内容</span></p><p>   <i>(注:多级引用可以使用多个>>)</i></p><blockquote><p>一级 ></p><blockquote><p>二级 >></p><blockquote><p>三级 >>></p></blockquote></blockquote></blockquote><h3 id="2-2-插入图片"><a href="#2-2-插入图片" class="headerlink" title="2.2 插入图片"></a>2.2 插入图片</h3><p>  快捷键 <kbd>Ctrl + G</kbd>,等效为 <span style="color:green">![imgName](url)</span><br><img src="/upload/markdown_1.png" alt="imgName"></p><h3 id="2-3-插入链接"><a href="#2-3-插入链接" class="headerlink" title="2.3 插入链接"></a>2.3 插入链接</h3><p>  语法为:<span style="color:green">[链接名称](url)</span></p><p>  <a href="https://www.baidu.com" target="_blank" rel="noopener">百度一下</a></p><h3 id="2-4-序列"><a href="#2-4-序列" class="headerlink" title="2.4 序列"></a>2.4 序列</h3><p>  (1)<strong>无序序列</strong> 快捷键 <kbd>Ctrl + U</kbd> 等效于 <span style="color:green">- 内容</span> 或者 <span style="color:green">+ 内容</span> 或者 <span style="color:green">* 内容</span></p><p>  (2)<strong>有序序列</strong> 快捷键 <kbd>Ctrl + Shift + O</kbd> 等效于 <span style="color:green">1. 列表</span></p><h3 id="2-5-代码引用"><a href="#2-5-代码引用" class="headerlink" title="2.5 代码引用"></a>2.5 代码引用</h3><p>  代码块:快捷键 <kbd>Ctrl + K</kbd>,等效为 <span style="color:green">4个以上的空格开头</span> 或者 <span style="color:green"> 3个反引号``` </span></p><p>  可嵌入代码行:快捷键 <kbd>Ctrl + K</kbd>, <span style="color:green"> `代码` </span></p><hr><hr><h3 id="3-1-表格"><a href="#3-1-表格" class="headerlink" title="3.1 表格"></a>3.1 表格</h3><pre><code>|标题|标题|标题||:---|:---:|---:||居左测试文本|居中测试文本|居右测试文本||居左测试文本1|居中测试文本2|居右测试文本3||居左测试文本11|居中测试文本22|居右测试文本33||居左测试文本111|居中测试文本222|居右测试文本333|</code></pre><table><thead><tr><th style="text-align:left">标题</th><th style="text-align:center">标题</th><th style="text-align:right">标题</th></tr></thead><tbody><tr><td style="text-align:left">居左测试文本</td><td style="text-align:center">居中测试文本</td><td style="text-align:right">居右测试文本</td></tr><tr><td style="text-align:left">居左测试文本1</td><td style="text-align:center">居中测试文本2</td><td style="text-align:right">居右测试文本3</td></tr><tr><td style="text-align:left">居左测试文本11</td><td style="text-align:center">居中测试文本22</td><td style="text-align:right">居右测试文本33</td></tr><tr><td style="text-align:left">居左测试文本111</td><td style="text-align:center">居中测试文本222</td><td style="text-align:right">居右测试文本333</td></tr></tbody></table>]]></content>
<summary type="html">
<p>&emsp;&emsp;Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式。<br>&emsp;&emsp;Markdown的语法简洁明了、学习容易,而且功能比纯文本更强,因此有很多人用它写博客。世界上最流行的
</summary>
<category term="markdown" scheme="http://yoursite.com/categories/markdown/"/>
<category term="markdown" scheme="http://yoursite.com/tags/markdown/"/>
</entry>
<entry>
<title>M2Crypto使用</title>
<link href="http://yoursite.com/2018/01/03/M2Crypto%E4%BD%BF%E7%94%A8/"/>
<id>http://yoursite.com/2018/01/03/M2Crypto使用/</id>
<published>2018-01-03T05:46:55.000Z</published>
<updated>2018-06-25T11:25:30.000Z</updated>
<content type="html"><![CDATA[<p>加解密数据、操作密钥、操作SSL协议普遍使用了OpenSSL。虽然还有其它的使用C/C++开发的加密处理库,但是Python环境下支持最好的使用最广泛的还是OpenSSL。 </p><p>据python.org官方网站,目前有三个库提供了OpenSSL的包装。 </p><ol><li>PyOpenSSL。这个库是比较早的,但是作者已经停止开发,并且只支持SSL功能,而没有提供加密、解密、X509等功能的包装。 </li><li>M2Crypto。完整支持OpenSSL。单元测试比较全面。在原有C语言API的基础上提供了Python的封装。 </li><li>ssl4py。与M2Crypto类似。但是完全使用C编写,与OpenSSL的API很类似。估计是用SWIG之类的工具生成的。据我本人看他的源代码,在调用EVP_CipherUpdate()函数的时候,输出大小没有计算正确。此错误会造成数据不正确,是一个比较严重的BUG。我估计应该还有其它的BUG存在,可能比较不成熟。 </li><li>ezPyCrypto。全名是Python Cryptography Toolkit。据水木网友josephpei说,这个很强大,有望进入官方CPython的标准库内。不过考虑到学习OpenSSL的API以后找工作比较好办,所以暂时不考虑。 </li></ol><p>综上所述,我在开发中使用M2Crypto。 </p><p>M2Crypto的API手册处于:<a href="http://www.heikkitoivonen.net/m2crypto/api/" target="_blank" rel="noopener">http://www.heikkitoivonen.net/m2crypto/api/</a> </p><p>目前,截止到2009年10月23日,官网上提供的M2Crypto for Python 2.6(win32)安装包是不正确的。因为它提供的0.19版本并没有兼容0.20。所以需要下载M2Crypto的源代码自行编译。以下是编译的步骤: </p><ol><li>下载安装mingw32:<a href="http://www.mingw.org" target="_blank" rel="noopener">http://www.mingw.org</a>. </li><li>下载安装 swig: <a href="http://www.swig.org。选择下载SWIG" target="_blank" rel="noopener">http://www.swig.org。选择下载SWIG</a> for python(win32)的版本。并且把swig的路径加入$PATH环境变量内。 </li><li>下载安装OpenSSL的Windows版本:<a href="http://www.slproweb.com/products/Win32OpenSSL.html" target="_blank" rel="noopener">http://www.slproweb.com/products/Win32OpenSSL.html</a> </li><li>把OpenSSL的include文件夹复制到Python的include文件夹内。把OpenSSL的几个库文件(*.a)复制到mingw32的lib文件夹内。 </li><li>OpenSSL for windows的库文件与for Unix版本名字有些不大一样。需要把libeay32.dll.a改名liblibeay32.a,把libssl32.dll.a改名libssleay32.a。测试的版本是0.9.8h </li><li>运行setup.py build -c mingw32 bdist_wininst </li><li>一切顺利的话在dist文件夹下可以找到安装程序。 </li></ol><blockquote><p>M2Crypto主页提供了一处描述如何在windows平台下使用msvc编译openssl和M2Crypto的链接。经过试验,该方法不能在mingw32下成功。不过在一个用户评论上描述了mingw32下的方法,当时没仔细看,害我搞了半天没成功。 </p></blockquote><hr><p>经过我测试,编译后的M2Crypto虽然导入正常,但是一旦使用BIO进行文件操作,M2Crypto就会异常退出。并打印出No AppLink这样的错误信息。如果不使用BIO的话,好像又没啥问题。 </p><hr><p>下面是几个模块的大致介绍: </p><pre><code>M2Crypto.BIO 用于操作IO抽象类型。 M2Crypto.BN 用于操作大数 M2Crypto.DH 用于操作Diffie-Hellman key exchange protocol M2Crypto.EVP 高级的加密解密接口。与直接使用具体的加密算法不同。使用该接口,可以用相同的编程方式,调用不同的算法处理数据。它包含了对称加密算法与非对称加密算法的支持。 M2Crypto.EC 椭圆曲线非对称加密算法 M2Crypto.DSA DSA非对称加密算法 M2Crypto.RSA RSA非对称加密算法 M2Crypto.Rand 操作随机数 M2Crypto.SSL 操作SSL协议 M2Crypto.X509 操作X509 </code></pre><p>接下来,我们通过日常的编程任务来看看如何使用这些接口。 </p><h4 id="一、如何使用MD5、SHA1等消息散列算法。"><a href="#一、如何使用MD5、SHA1等消息散列算法。" class="headerlink" title="一、如何使用MD5、SHA1等消息散列算法。"></a>一、如何使用MD5、SHA1等消息散列算法。</h4><p>虽然OpenSSL提供了直接操作MD5、SHA1算法以及blowfish等各种对称加密算法的API,但是M2Crypto并没有将其包含进来。不过也好,各种算法都有各自的API,记起来麻烦。通过M2Crypto.EVP,我们仍然可以调用这些算法。下面是一个MD5的例子: </p><pre><code>def md5(s): m=EVP.MessageDigest("md5") #在构造函数中传入算法的名字可以选择不同的消息散列算法 m.update(s) return m.digest() #或者m.final() </code></pre><p>常用的散列算法还有sha1。使用方法与MD5类似,只是构造函数是: </p><pre><code>m=EVP.MessageDigest("sha1") </code></pre><h4 id="二、使用对称加密算法加密数据。"><a href="#二、使用对称加密算法加密数据。" class="headerlink" title="二、使用对称加密算法加密数据。"></a>二、使用对称加密算法加密数据。</h4><p>如前所述,我们需要使用EVP.Cipher这个比较抽象的API,而不是具体的算法。与EVP.MessageDigest()类似,EVP.Cipher主要提供四个函数: </p><pre><code>EVP.Cipher.__init__(self, alg, key, iv, op, key_as_bytes=0, d='md5', salt='12345678', i=1, padding=1) EVP.Cipher.update(self, data) EVP.Cipher.final() EVP.Cipher.set_padding(self, padding=1) </code></pre><p>下面是一段使用blowfish算法将明文”fish is here”加密成密文的函数代码: </p><pre><code>def blowfish_encrypt(s, password): out=StringIO() m=EVP.Cipher("bf_ecb", password, "123456", 1, 1, "sha1", "saltsalt", 5, 1) out.write(m.update(s)) out.write(m.final()) return out.getvalue() </code></pre><p>可以发现,最主要的是Cipher的构造函数: </p><pre><code>EVP.Cipher.__init__(self, alg, key, iv, op, key_as_bytes=0, d='md5', salt='12345678', i=1, padding=1) </code></pre><p>alg是指算法的名字,OpenSSL支持以下算法: </p><blockquote><p>des_cbc des_ecb des_cfb des_ofb<br>des_ede_cbc des_ede des_ede_ofb des_ede_cfb     2DES算法<br>des_ede3_cbc des_ede3 des_ede3_ofb des_ede3_cfb   3DES算法<br>desx_cbc<br>rc4<br>rc4_40    密钥为40位的RC4算法<br>idea_cbc idea_ecb idea_cfb idea_ofb idea_cbc<br>rc4_cbc rc2_ecb rc2_cfb rc2_ofb<br>rc2_40_cbc rc2_64_cbc<br>bf_cbc bf_ecb bf_cfb bv_ofb    Blowfish算法<br>cast5_cbc cast5_ecb cast5_cfb cast5_ofb<br>rc5_32_12_16_cbc rc5_32_12_16_ecb rc5_32_12_16_cfb rc5_32_12_16_ofb </p></blockquote><p>key是加密所用的密钥。传入的是一段二进制数据,其长度是密钥的长度。不过,如果后面的参数key_as_bytes==1,那key是一个普通的任意长度的字符串,将与salt,i参数一起生成一个真正的密钥。比如说,假设算法alg的密钥长度是16,如果key_as_bytes==0,那么key应该传入”\xff\xff”两个字节的字符串。如果key_as_bytes==1,则可以传入类似于123456这样子的字符串。 </p><p>iv是指初始向量。与加密算法所使用的加密块的长度一致。有些加密算法并不使用iv这个变量。如果key_as_bytes==1。虽然OpenSSL的key_to_bytes()函数可以使用alt,key,salt,d,i四个参数生成真正的密钥和iv。但是M2Crypto内部并没有这样子做。而是直接使用原来的iv.如果iv的长度超过了加密算法所使用的加密块的长度,超过的长度会被截取。 </p><p>op用于指示解密或者加密操作。op==1表示加密操作;op==0表示解密操作。在做逆操作的时候,除了op不一样,其它参数应当保持一致。 </p><p>key_as_bytes参数如前所述。如果key_as_bytes==1。M2Crypto会使用alg, key, d, salt, i五个参数生成真正的密钥(注意,没有使用IV)。如果key_as_bytes==0,表示传入的是真正的密钥,d, salt, i三个参数就没有意义了。 </p><p>d是指生成密钥时所使用的散列算法。可以选择md5, sha1等。最好使用sha1,因为md5的破解看来只是时间问题了。 </p><p>salt是指生成密钥时所使用的盐。M2Crypto默认是123456。 </p><p>i是指生成密钥时所迭代的次数。迭代次数越多,使用暴力攻击就越不容易。 </p><p>padding是指填充加密块。大多数加密算法是以块为单位进行加密的。明文被切分为一个个固定大小的块。然后分别进行加密,得到与原来大小一致的加密块。但是明文的长度并不一定是加密块长度的整数倍。因此在处理最后一个块时需要进行填充。常用的填充算法是PKCS padding.如果没有允许padding并且最后一段明文不足以达到加密块的长度。EVP_EncryptFinal_ex()会返回一个错误。如果padding是允许的,但是密文最后并没有包含一个正确的填充块,EVP_DecryptoFinal()就会返回一个错误。padding默认是允许的。 </p><h4 id="三、-生成RSA密钥"><a href="#三、-生成RSA密钥" class="headerlink" title="三、 生成RSA密钥"></a>三、 生成RSA密钥</h4><p>DSA与RSA是比较常用的两种非对称加密算法。他们的使用方法与特性正如他们的名字,基本上大同小异。在OpenSSL内,使用与其它名字一样的结构体来表示这两个算法的密钥。在M2Crypto里,也是如此。只是在M2Crypto里DSA与RSA是两个类,带有签名、验证等方法。 </p><p>一般并不构造RSA与DSA类。而使用相应的工厂方法。比如生成RSA密钥: </p><pre><code>from M2Crypto import BIO, RSA def genrsa(): #这函数生成一个1024位的RSA密钥,将其转化成PEM格式返回 bio=BIO.MemoryBuffer() rsa=RSA.gen_key(1024, 3, lambda *arg:None) rsa.save_key_bio(bio, None) return bio.read_all() </code></pre><p>RSA.gen_key()是一个工厂方法,它返回一个存储了新的RSA密钥的RSA.RSA()实例。它的方法签名是: </p><pre><code>gen_key(bits, e, callback=keygen_callback) </code></pre><p>bits参数是指RSA密钥的长度,1024以下的RSA密钥虽然还没有被破解,但是已经认为是不安全的了。作为CA使用的RSA密钥通常要求达到2048位以上。<br>e是RSA算法的public exponent。功能是什么?我也不大清楚,据OpenSSL的文档说,这个函数通常是三个奇数3,17,65537之一。<br>callback是一个回调函数。用于显示生成密钥的进度。具体请查阅OpenSSL的文档。 </p><p>这里是OpenSSL中对应的函数原型: </p><pre><code>#include <openssl/rsa.h> RSA *RSA_generate_key(int num, unsigned long e,void (*callback)(int,int,void *), void *cb_arg); </code></pre><h4 id="四、生成DSA密钥"><a href="#四、生成DSA密钥" class="headerlink" title="四、生成DSA密钥"></a>四、生成DSA密钥</h4><p>DSA算法相关的估计是另外的人开发的。API有些不大一样。它首先需要生成参数,然后才能生成密钥。以下是一段代码: </p><pre><code>from M2Crypto import BIO, DSA def gendsa(): #这函数生成一个1024位的DSA密钥,将其转化成PEM格式返回 bio=BIO.MemoryBuffer() dsa = DSA.gen_params(1024, lambda *arg: None) dsa.gen_key() dsa.save_key_bio(bio,None) return bio.read_all() </code></pre><p>可以发现生成DSA密钥时需要首先使用DSA.gen_params()生成DSA参数。gen_params()函数的第一个参数是DSA密钥的长度,第二个密钥与RSA.gen_key()的回调函数相同。DSA.gen_params()返回一个DSA类的实例。调用DSA.gen_key()方法生成密钥。其它的与RSA类似。 </p><h4 id="五、载入DSA密钥与RSA密钥"><a href="#五、载入DSA密钥与RSA密钥" class="headerlink" title="五、载入DSA密钥与RSA密钥"></a>五、载入DSA密钥与RSA密钥</h4><p>RSA:<br>返回RSA类型: </p><pre><code>load_key(file, callback=util.passphrase_callback) load_key_bio(bio, callback=util.passphrase_callback) load_key_string(string, callback=util.passphrase_callback) </code></pre><p>返回RSA_pub类型: </p><pre><code>load_pub_key(file) load_pub_key_bio(bio) </code></pre><p>DSA:<br>返回DSA类型: </p><pre><code>load_params(file, callback=util.passphrase_callback) load_params_bio(bio, callback=util.passphrase_callback) load_key(file, callback=util.passphrase_callback) load_key_bio(bio, callback=util.passphrase_callback) </code></pre><p>返回DSA_pub类型: </p><pre><code>load_pub_key(file, callback=util.passphrase_callback) load_pub_key_bio(bio, callback=util.passphrase_callback) </code></pre><p>这些函数大同小异。如果参数名字是file的话,代表的是一个文件名。如果参数名字是bio的话,代表的是一个BIO对像。BIO对象与Python的file对象类似都是用于表示一个可以读写的类似于文件的类型。BIO对象除了可以是一个普通的文件,还可以是一个ssh连接,还可以是一段内存(BIO.MemoryBuffer)。BIO.MemoryBuffer与Python的StringIO.StringIO类似。因为之前我们提到我编译的M2Crypto在进行文件IO的时候会异常退出,所以最好只使用BIO.MemoryBuffer。 </p><p>在本文里,提到密钥,是同时指公钥与私钥。<br>在OpenSSL及大多数软件里,因为公钥会被单独分发出去,所以公钥可以单独保存在公钥文件里。而密钥的所有者既然保存私钥,肯定也会同时保存公钥。故而私钥并不会单独保存到一个私钥文件里,而是和公钥一起保存在密钥文件里。 </p><h4 id="六、RSA类型的操作——使用RSA加密、解密、签名、认证;保存RSA密钥"><a href="#六、RSA类型的操作——使用RSA加密、解密、签名、认证;保存RSA密钥" class="headerlink" title="六、RSA类型的操作——使用RSA加密、解密、签名、认证;保存RSA密钥"></a>六、RSA类型的操作——使用RSA加密、解密、签名、认证;保存RSA密钥</h4><p>RSA类型封装了一些可以使用RSA密钥进行的操作。 </p><p>首先,可以使用len(rsa)获得RSA密钥的长度。单位是位,通常使用1024位以上的密钥才是安全的。 </p><p>public_encrypt(self, data, padding):<br>使用公钥进行加密。data是数据,padding参数是指是否填充加密块。具体的含义可以看看EVP.Cipher类的构造函数。data是一段普通的字符串,而padding的类型是布尔型。 </p><pre><code>def public_decrypt(self, data, padding): </code></pre><p>使用公钥进行解密。 </p><pre><code>def private_encrypt(self, data, padding): </code></pre><p>使用私钥进行加密。 </p><pre><code>def private_decrypt(self, data, padding): </code></pre><p>使用公钥进行加密。 </p><p>因为RSA是一种非对称加密算法。所以用私钥加密的数据,要用公钥才能解密。反之,用公钥加密的数据,要用私钥才能解密。通常在通信中,发送方使用接收方的公钥加密数据。<em>只有</em>接收方才有私钥能够解密数据。因为非对称加密算法的速度一般比对称加密算法慢,所以在一个连续的通信过程中,经常是发送方随机生成一个对称加密算法的密钥,然后使用非对称加密算法发送给接收方,以后所有的通信过程都是使用这个随机密钥。只要保证每隔一段时间就换一个密钥,这个通信过程就跟直接使用非对称加密算法一样安全了。类似于电子邮件这样的通信过程中,双方商量一个随机密钥的时间很长,所以还是乖乖直接用公钥加密的好。 </p><p>非对称算法还经常用于对数据进行签名。签名可以保证发送方不能否认自己发送的数据是自己的。比如,在一个电子商务交易中,客户下了一个订单,不能等工厂已经生产完了才否认这个订单是自己下的。签名最简单的办法当然是使用发送方的私钥进行加密。如果不使用发送方的公钥就不能解密数据。反之,也可以说,凡是可以使用发送方的公钥解密出数据,就说明数据是使用发送方的私钥加密的。在现实生活中,人们一般是使用SHA1之类的散列算法算出数据的散列值,然后再用私钥加密这个散列值。接收方接收到数据与散列值之后,同样使用SHA1算法算出数据的散列值,与使用公钥解密出来的散列值作对比。如果是一样的,说明数据正确。如果不一样,或者是在传输过程中被更改了,或者根本不是发送方所发送的。 </p><p>RSA算法提供了两种签名的方式,其分别可能是不同的国际标准。我还不是很清楚。 </p><pre><code>sign_rsassa_pss(self, digest, algo='sha1', salt_length=20); verify_rsassa_pss(self, data, signature, algo='sha1', salt_length=20) </code></pre><p>这组API与下面两个函数类似。看起来差不多的样子,不过我没有进行过测试。实际上OpenSSL中并没有sign_rsassa_pss()这样函数。它实际上是分为两个步骤: </p><pre><code>RSA_padding_add_PKCS1_PSS()和 RSA_private_encrypt() </code></pre><p>而verify_rsassa_pss()函数则分为 </p><pre><code>RSA_public_decrypt()与 RSA_verify_PKCS1_PSS() 两个步骤 sign(self, digest, algo='sha1'): verify(self, data, signature, algo='sha1') </code></pre><p>这组API对应于OpenSSL中的RSA_sign()与RSA_verify()函数。分别是签名与验证。虽然sign()方法接收散列算法的名字作为名字,但实际上digest参数应该是已经计算出的散列值。以下是对一段数据进行签名的代码: </p><p>发送方对数据进行签名 </p><pre><code>from M2Crypto import * m=EVP.EVP.MessageDigest("sha1") #先计算散列值 m.update("fish is here") digest=m.final() key_str=file("fish_private.pem","rb").read() #读入私钥 key=RSA.load_key_string(key_str, util.no_passphrase_callback) result=key.sign(digest, "sha1") #签名后得到的数据。与原始数据一起发送出去。 </code></pre><p>接收方验证数据 </p><pre><code>from M2Crypto import * m=EVP.EVP.MessageDigest("sha1") #先计算散列值 m.update("fish is here") digest=m.final() #先计算散列值 cert_str=file("fish_public.pem", "rb").read() #读入公钥 mb=BIO.MemoryBuffer(cert_str) cert=RSA.load_pub_key_bio(mb) #RSA模式没有load_pub_key_string()方法,需自行使用MemoryBuffer cert.verify(digest, result, "sha1") </code></pre><h4 id="七、一个小型的CA,电子证书。"><a href="#七、一个小型的CA,电子证书。" class="headerlink" title="七、一个小型的CA,电子证书。"></a>七、一个小型的CA,电子证书。</h4><p>说到CA,不得不说到PKI认证体系。PKI体系是一个概念性的认证系统。它的基本原理是,通信系统内所有节点都承认一个权威的机构,称为CA。所有参与通信的节点都有一个电子证书,由该节点的公钥和身份认证信息组成。CA核查这个电子证书的身份信息是否正确。如果正确的话,就使用CA的秘钥进行签名。这样,所有的通信节点就可以使用电子证书内包含的身份信息而不必亲自核查了。 </p><p>所有通信节点都持有CA的电子证书。CA的电子证书是CA自己签名的。在PKI系统运行之前,人们会事先设置好CA证书。 </p><p>通信节点生成电子证书的过程是:<br>1、通信节点生成RSA或者DSA等非对称加密算法的密钥。<br>2、通信节点生成电子证书请求文件(X509_Request)。其中包含通信节点的身份信息、公钥,并且用通信节点的私钥签名。<br>3、CA读取电子证书请求文件。核查身份信息是否正确。如果身份信息正确就生成通信节点的电子证书。其中包含CA的身份信息、通信节点的身份信息、通信节点的公钥、电子证书的起始有效时间,电子证书的结束有效时间。通常还会记录这是CA所颁发的第几个证书。</p><p>生成非对称加密算法的密钥在之前已经有提到过了。接下来是如何生成证书请求文件的代码: </p><pre><code>from M2Crypto import * #首先载入密钥文件。此文件同时保存了通信节点的私钥与公钥。 #这里并不像之前直接使用 pkey_str=file("fish_private.pem", "rb").read() pkey=EVP.load_key_string(pkey_str, util.no_passphrase_callback) req=X509.Request() req.set_pubkey(pkey) #包含公钥 #req.set_version(1) name=X509.X509_Name() #身份信息不是简单的字符串。而是X509_Name对象。 name.CN="Goldfish" #CN是Common Name的意思。如果是一个网站的电子证书,就要写成网站的域名 name.OU="Manager" #Organization Unit,通常是指部门吧,组织内单元 name.O="ZieglerT" #Organization。通常是指公司 name.ST="Fujian" #State or Province。州或者省 name.L="Quanzhou" #Locale。 name.C="CN" #国家。不能直接写国家名字,比如China之类的,而应该是国家代码。CN代表中国。US代表美国,JP代表日本 req.set_subject(name) #包含通信节点的身份信息 req.sign(pkey, "sha1") #使用通信节点的密钥进行签名 file("fish_req.pem", "wb").write(req.as_pem()) #写入到文件 </code></pre><p>可以发现,如果简化那些设置身份信息的代码,实际上就是三步:包含公钥、包含身份信息、签名。 </p><p>接下来,我们看看CA是如何给一个通信节点发放证书的。 </p><pre><code>from M2Crypto import * import time #首先读取证书请求文件。 req_str=file("fish_req.pem", "rb").read() req=X509.load_request_string(req_str) #返回一个X509.Request类型代表证书请求文件 print req.verify(req.get_pubkey()) #首先验证一下,是不是真的是使用它本身的私钥签名的。如果是,返回非0值。如果不是,说明这是一个非法的证书请求文件。 #接下来载入CA的电子证书。与CA的密钥不一样,CA的电子证书包含了CA的身份信息。CA的电子证书会分发给各个通信节点。 ca_str=file("ca.pem", "rb").read() ca=X509.load_cert_string(ca_str) #print ca.check_ca() #可以使用check_ca()方法判断这个证书文件是不是CA。本质是判断它是不是自签名。如果是的话,就返回非0值。如果不是的话就返回0。 #接下来载入CA的密钥 cakey_str=file("cakey.pem", "rb").read() cakey=EVP.load_key_string(cakey_str, lambda *args:"1234") #一般CA的密钥要加密保存。回调函数返回密码 #接下来开始生成电子证书 cert=X509.X509() #首先,设定开始生效时间与结束生效时间 t = long(time.time()) + time.timezone #当前时间,单位是秒 now = ASN1.ASN1_UTCTIME() #开始生效时间。证书的时间类型不是普通的Python datetime类型。 now.set_time(t) nowPlusYear = ASN1.ASN1_UTCTIME() #结束生效时间 nowPlusYear.set_time(t + 60 * 60 * 24 * 365) #一年以后。 cert.set_not_before(now) cert.set_not_after(nowPlusYear) cert.set_subject(req.get_subject()) #把证书请求附带的身份信息复制过来 cert.set_issuer(ca.get_subject()) #设置颁发者的身份信息,把CA电子证书内身份信息复制过来 cert.set_serial_number(2) #序列号是指,CA颁发的第几个电子证书文件 cert.set_pubkey(req.get_pubkey()) #把证书请求内的公钥复制过来 cert.sign(cakey, "sha1") #使用CA的秘钥进行签名。 file("fishcert2.pem", "wb").write(cert.as_pem()) #保存文件。 </code></pre>]]></content>
<summary type="html">
<p>加解密数据、操作密钥、操作SSL协议普遍使用了OpenSSL。虽然还有其它的使用C/C++开发的加密处理库,但是Python环境下支持最好的使用最广泛的还是OpenSSL。 </p>
<p>据python.org官方网站,目前有三个库提供了OpenSSL的包装。 </p>
</summary>
<category term="python" scheme="http://yoursite.com/categories/python/"/>
<category term="python" scheme="http://yoursite.com/tags/python/"/>
<category term="crypt" scheme="http://yoursite.com/tags/crypt/"/>
<category term="rsa" scheme="http://yoursite.com/tags/rsa/"/>
</entry>
</feed>