1. 程式人生 > 資料庫 >tornado連線mysql資料庫與pymysql的簡單操作

tornado連線mysql資料庫與pymysql的簡單操作

reference:

 本人的python是3.5,由於3.0後用的是pymysql,就不能用tornado自帶的torndb來進行簡單的連線操作。

Application這個類是初始化一些全域性變數,按照道理說裡邊的self.db 也應該能夠被其他類或者派生類呼叫的,但是

db這個屬性就是不行,無奈只好建立了一個全域性的db控制代碼,然後在HouseHandler類中根據這個db初始化一個例項。

當然要在Aplication中傳入這個字典引數:dict(db=db)

 
  1.  
  2. # -*- coding: utf-8 -*-

  3. """

  4. Created on Wed Mar 21 16:49:24 2018

  5.  
  6.  
  7. @author: ming

  8. """

  9.  
  10.  
  11. # coding:utf-8

  12. import os

  13. import tornado.web

  14. import tornado.ioloop

  15. import tornado.httpserver

  16. import tornado.options

  17. from tornado.options import options, define

  18. from tornado.web import RequestHandler

  19. #import torndb

  20. import pymysql

  21. '''

  22. python 2用torndb

  23. '''

  24.  
  25.  
  26. define("port", default=8000, type=int, help="run server on the given port.")

  27. db =  pymysql.Connection(host='127.0.0.1', database='mysql', user='root', password='0000',charset='utf8') 

  28. class HouseHandler(RequestHandler):

  29.     def initialize(self, db):

  30.         self.db = db

  31.         print(1)

  32.     def get(self):

  33.         #db = self.db

  34.         cur=db.cursor()

  35.         print(type(cur))

  36.         try:

  37.              cur.execute("insert into houses(title, position, price, score, comments) values(%s, %s, %s, %s, %s)", ('獨立裝修小別 墅', '緊鄰文津街', 280, 4, 128) )

  38.         except Exception as e:

  39.             return self.write('cuo wu')

  40.         db.commit()

  41.         print("success")

  42.         cur.close()

  43.    # db.close()

  44.             

  45.  #self.write({"error":0,"errmsg":"db ok","data":[]})

  46. #這個類把登入資訊進行了繫結,保證連線的時候只例項化一次

  47. class Application(tornado.web.Application): 

  48.     def _init_(self,*args,**kwargs):

  49.         self.a =1;

  50.         

  51.         super(Application,self)._init_(*args,**kwargs)

  52.         #img_files = files.get('img')

  53.         '''try:

  54.             self.db =  pymysql.Connection(host='127.0.0.1', database='mysql', user='root', password='0000') 

  55.  
  56.         except Exception as e:

  57.             #發生錯誤就不往下執行,而是向前端返回出錯資訊

  58.          return self.write("haha")'''

  59.         print("hahaaa")

  60.               

  61.               

  62. settings = dict(

  63.           template_path=os.path.join(os.path.dirname(__file__), "templates"),

  64.           static_path=os.path.join(os.path.dirname(__file__), "statics"),

  65.           debug=True, 

  66.           )

  67. if __name__ == "__main__":

  68.     tornado.options.parse_command_line()

  69.     app = Application([

  70.         #(r"/", IndexHandler),

  71.         (r"/house", HouseHandler,dict(db=db)),

  72.     ],**settings)

  73.     http_server = tornado.httpserver.HTTPServer(app)

  74.     http_server.listen(options.port)

  75.     tornado.ioloop.IOLoop.current().start()

二:pymysql的簡單操作

     (1)網上有個模擬注入攻擊的例子

在這個例項中不是建立一個全域性的連線,而是在post方法中建立一個連線,這種做法不提倡。

 

一、搭建環境

1、服務端的tornado主程式app.py如下:

 
  1. #!/usr/bin/env python3

  2. # -*- coding: utf-8 -*-

  3.  
  4. import tornado.ioloop

  5. import tornado.web

  6. import pymysql

  7.  
  8. class LoginHandler(tornado.web.RequestHandler):

  9. def get(self):

  10. self.render('login.html')

  11.  
  12. def post(self, *args, **kwargs):

  13. username = self.get_argument('username',None)

  14. pwd = self.get_argument('pwd', None)

  15.  
  16. # 建立資料庫連線

  17. conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='shop')

  18. cursor = conn.cursor()

  19.  
  20. # %s 要加上'' 否則會出現KeyboardInterrupt的錯誤

  21. temp = "select name from userinfo where name='%s' and password='%s'" % (username, pwd)

  22. effect_row = cursor.execute(temp)

  23. result = cursor.fetchone()

  24. conn.commit()

  25. cursor.close()

  26. conn.close()

  27.  
  28. if result:

  29. self.write('登入成功!')

  30. else:

  31. self.write('登入失敗!')

  32.  
  33.  
  34. settings = {

  35. 'template_path':'template',

  36. }

  37.  
  38.  
  39. application = tornado.web.Application([

  40. (r"/login", LoginHandler),

  41. ],**settings)

  42.  
  43. if __name__ == "__main__":

  44. application.listen(8000)

  45. tornado.ioloop.IOLoop.instance().start()

2、在template資料夾下,放入login.html檔案:

 
  1. <!DOCTYPE html>

  2. <html lang="en">

  3. <head>

  4. <meta charset="UTF-8">

  5. <title>Title</title>

  6. </head>

  7. <body>

  8. <form method="post" action="/login">

  9. <input type="text" name="username" placeholder="使用者名稱"/>

  10. <input type="text" name="pwd" placeholder="密碼"/>

  11. <input type="submit" value="提交" />

  12. </form>

  13. </body>

  14. </html>

3、在shop資料庫中建立userinfo資料表,並填入資料:

隨便新增兩條就好,明文就明文吧:

二、模擬登入

1、正常登入





以上都是“好使用者”的正常登入,我們看一下“壞傢伙”怎麼做。

2、非法登入

密碼不對也能登入:


看一下服務端執行的SQL語句,就不難理解了,密碼部分被註釋掉了:

select name from userinfo where name='dyan' -- n' and password='000'

賬戶密碼都不對照樣登入成功:


看執行的SQL語句:

select name from userinfo where name='badguy' or 1=1 -- y' and password='000'

三、使用cursor.execute方式防止注入

使用字串拼接的方式會導致SQL注入。在cursor.execute方法中對'導致注入的符號做了轉義。

將app.py中下面兩行程式碼改為:

 
  1. # 導致SQL注入

  2. temp = "select name from userinfo where name='%s' and password='%s'" % (username, pwd)

  3. effect_row = cursor.execute(temp)

  4.  
 
  1. # 防止SQL注入

  2. effect_row = cursor.execute("select name from userinfo where name='%s' and password='%s'",(username, pwd,))

再次嘗試注入:


錯誤原因,巴拉巴拉就是語法不對:

ymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax;

看看內部執行的語句,主要是對'符號做了轉義防止注入:

select name from userinfo where name=''dyan\' -- n'' and password=''123''

 

二、使用操作

1. 執行SQL

 
  1. #!/usr/bin/env python

  2. # _*_ coding:utf-8 _*_

  3. __author__ = 'junxi'

  4.  
  5. import pymysql

  6.  
  7. # 建立連線

  8. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  9.  
  10. # 建立遊標, 查詢資料預設為元組型別

  11. cursor = conn.cursor()

  12.  
  13.  
  14. # 執行SQL,並返回收影響行數

  15. row1 = cursor.execute("update users set password = '123'")

  16. print(row1)

  17. # 執行SQL,並返回受影響行數

  18. row2 = cursor.execute("update users set password = '456' where id > %s", (1,))

  19. print(row2)

  20. # 執行SQL,並返回受影響行數(使用pymysql的引數化語句防止SQL注入)

  21. row3 = cursor.executemany("insert into users(username, password, email)values(%s, %s, %s)", [("ceshi3", '333', '[email protected]'), ("ceshi4", '444', '[email protected]')])

  22. print(row3)

  23.  
  24. # 提交,不然無法儲存新建或者修改的資料

  25. conn.commit()

  26. # 關閉遊標

  27. cursor.close()

  28. # 關閉連線

  29. conn.close()

  30.  

提示:存在中文的時候,連線需要新增charset='utf8',否則中文顯示亂碼。

2、獲取查詢資料

 
  1. #!/usr/bin/env python

  2. # _*_ coding:utf-8 _*_

  3. __author__ = 'junxi'

  4.  
  5. import pymysql

  6.  
  7. # 建立連線

  8. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  9.  
  10. # 建立遊標, 查詢資料預設為元組型別

  11. cursor = conn.cursor()

  12. cursor.execute("select * from users")

  13.  
  14. # 獲取第一行資料

  15. row_1 = cursor.fetchone()

  16. print(row_1)

  17. # 獲取前n行資料

  18. row_n = cursor.fetchmany(3)

  19. print(row_n)

  20. # 獲取所有資料

  21. row_3 = cursor.fetchall()

  22. print(row_3)

  23.  
  24.  
  25. # 提交,不然無法儲存新建或者修改的資料

  26. conn.commit()

  27. # 關閉遊標

  28. cursor.close()

  29. # 關閉連線

  30. conn.close()

  31.  

3、獲取新建立資料自增ID
可以獲取到最新自增的ID,也就是最後插入的一條資料ID

 
  1. #!/usr/bin/env python

  2. # _*_ coding:utf-8 _*_

  3. __author__ = 'junxi'

  4.  
  5. import pymysql

  6.  
  7. # 建立連線

  8. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  9.  
  10. # 建立遊標, 查詢資料預設為元組型別

  11. cursor = conn.cursor()

  12.  
  13. cursor.executemany("insert into users(username, password, email)values(%s, %s, %s)", [("ceshi3", '333', '[email protected]'), ("ceshi4", '444', '[email protected]')])

  14. new_id = cursor.lastrowid

  15. print(new_id)

  16.  
  17.  
  18. # 提交,不然無法儲存新建或者修改的資料

  19. conn.commit()

  20. # 關閉遊標

  21. cursor.close()

  22. # 關閉連線

  23. conn.close()

  24.  

4、移動遊標
操作都是靠遊標,那對遊標的控制也是必須的

 
  1. 注:在fetch資料時按照順序進行,可以使用cursor.scroll(num,mode)來移動遊標位置,如:

  2.  
  3. cursor.scroll(1,mode='relative') # 相對當前位置移動

  4. cursor.scroll(2,mode='absolute') # 相對絕對位置移動

5、fetch資料型別
關於預設獲取的資料是元組型別,如果想要或者字典型別的資料,即:

 
  1. import pymysql

  2.  
  3. # 建立連線

  4. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  5.  
  6. # 遊標設定為字典型別

  7. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

  8. # 左連線查詢

  9. r = cursor.execute("select * from users as u left join articles as a on u.id = a.user_id where a.user_id = 2")

  10. result = cursor.fetchall()

  11. print(result)

  12.  
  13. # 查詢一個表的所有欄位名

  14. c = cursor.execute("SHOW FULL COLUMNS FROM users FROM blog")

  15. cc = cursor.fetchall()

  16.  
  17.  
  18. # 提交,不然無法儲存新建或者修改的資料

  19. conn.commit()

  20. # 關閉遊標

  21. cursor.close()

  22. # 關閉連線

  23. conn.close()

  24.  

檢視執行結果:

[{'user_id': 2, 'id': 2, 'password': '456', 'email': '[email protected]', 'a.id': 2, 'content': '成名之路', 'title': '星光大道', 'username': 'tangtang'}]

6、呼叫儲存過程

a、呼叫無參儲存過程

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. #遊標設定為字典型別

  8. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

  9. #無引數儲存過程

  10. cursor.callproc('p2') #等價於cursor.execute("call p2()")

  11.  
  12. row_1 = cursor.fetchone()

  13. print row_1

  14.  
  15.  
  16. conn.commit()

  17. cursor.close()

  18. conn.close()

  19.  

b、呼叫有參儲存過程

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

  8.  
  9. cursor.callproc('p1', args=(1, 22, 3, 4))

  10. #獲取執行完儲存的引數,引數@開頭

  11. cursor.execute("select @p1,@_p1_1,@_p1_2,@_p1_3")

  12. # {u'@_p1_1': 22, u'@p1': None, u'@_p1_2': 103, u'@_p1_3': 24}

  13. row_1 = cursor.fetchone()

  14. print row_1

  15.  
  16.  
  17. conn.commit()

  18. cursor.close()

  19. conn.close()

  20.  

三、關於pymysql防注入

1、字串拼接查詢,造成注入

正常查詢語句:

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. cursor = conn.cursor()

  8. username = "ceshi1"

  9. password = "ceshi1passwd"

  10. # 正常構造語句的情況

  11. sql = "select username, password from users where user='%s' and pass='%s'" % (username, password)

  12. # sql = select username, password from users where user='ceshi1' and pass='ceshi1passwd'

  13. row_count = cursor.execute(sql)

  14. row_1 = cursor.fetchone()

  15. print row_count, row_1

  16.  
  17. conn.commit()

  18. cursor.close()

  19. conn.close()

  20.  

構造注入語句:

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. cursor = conn.cursor()

  8.  
  9. username = "u1' or '1'-- "

  10. password = "u1pass"

  11. sql="select username, password from users where username='%s' and password='%s'" % (username, password)

  12.  
  13. # 拼接語句被構造成下面這樣,永真條件,此時就注入成功了。因此要避免這種情況需使用pymysql提供的引數化查詢。

  14. # select user,pass from tb7 where user='u1' or '1'-- ' and pass='u1pass'

  15.  
  16. row_count = cursor.execute(sql)

  17. row_1 = cursor.fetchone()

  18. print row_count,row_1

  19.  
  20.  
  21. conn.commit()

  22. cursor.close()

  23. conn.close()

  24.  

2、避免注入,使用pymysql提供的引數化語句
正常引數化查詢

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4.  
  5. import pymysql

  6.  
  7. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  8. cursor = conn.cursor()

  9. username="u1"

  10. password="u1pass"

  11. #執行引數化查詢

  12. row_count=cursor.execute("select username,password from tb7 where username=%s and password=%s",(username,password))

  13. row_1 = cursor.fetchone()

  14. print row_count,row_1

  15.  
  16. conn.commit()

  17. cursor.close()

  18. conn.close()

  19.  

構造注入,引數化查詢注入失敗。

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. cursor = conn.cursor()

  8.  
  9. username="u1' or '1'-- "

  10. password="u1pass"

  11. #執行引數化查詢

  12. row_count=cursor.execute("select username,password from users where username=%s and password=%s",(username,password))

  13. #內部執行引數化生成的SQL語句,對特殊字元進行了加\轉義,避免注入語句生成。

  14. # sql=cursor.mogrify("select username,password from users where username=%s and password=%s",(username,password))

  15. # print sql

  16. #select username,password from users where username='u1\' or \'1\'-- ' and password='u1pass'被轉義的語句。

  17.  
  18. row_1 = cursor.fetchone()

  19. print row_count,row_1

  20.  
  21. conn.commit()

  22. cursor.close()

  23. conn.close()

  24.  

結論:excute執行SQL語句的時候,必須使用引數化的方式,否則必然產生SQL注入漏洞。

3、使用存mysql儲過程動態執行SQL防注入

 
  1. 使用MYSQL儲存過程自動提供防注入,動態傳入SQL到儲存過程執行語句。

  2. delimiter \\

  3. DROP PROCEDURE IF EXISTS proc_sql \\

  4. CREATE PROCEDURE proc_sql (

  5. in nid1 INT,

  6. in nid2 INT,

  7. in callsql VARCHAR(255)

  8. )

  9. BEGIN

  10. set @nid1 = nid1;

  11. set @nid2 = nid2;

  12. set @callsql = callsql;

  13. PREPARE myprod FROM @callsql;

  14. -- PREPARE prod FROM 'select * from users where nid>? and nid<?'; 傳入的值為字串,?為佔位符

  15. -- 用@p1,和@p2填充佔位符

  16. EXECUTE myprod USING @nid1,@nid2;

  17. DEALLOCATE prepare myprod;

  18.  
  19. END\\

  20. delimiter ;

  21. set @nid1=12;

  22. set @nid2=15;

  23. set @callsql = 'select * from users where nid>? and nid<?';

  24. CALL proc_sql(@nid1,@nid2,@callsql)

  25.  

pymsql中呼叫

 
  1. #! /usr/bin/env python

  2. # -*- coding:utf-8 -*-

  3.  
  4. import pymysql

  5.  
  6. conn = pymysql.connect(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8')

  7. cursor = conn.cursor()

  8. sql1="select * from users where nid>? and nid<?"

  9. cursor.callproc('proc_sql', args=(11, 15, sql1))

  10.  
  11. rows = cursor.fetchall()

  12. print rows

  13. conn.commit()

  14. cursor.close()

  15. conn.close()

  16.  

四、使用with簡化連線過程

 
  1. # 使用with簡化連線過程,每次都連線關閉很麻煩,使用上下文管理,簡化連線過程

  2. import pymysql

  3. import contextlib

  4.  
  5.  
  6. # 定義上下文管理器,連線後自動關閉連線

  7. @contextlib.contextmanager

  8. def mysql(host='127.0.0.1', port=3306, user='blog', passwd='123456', db='blog', charset='utf8'):

  9. conn = pymysql.connect(host=host, port=port, user=user, passwd=passwd, db=db, charset=charset)

  10. cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

  11. try:

  12. yield cursor

  13. finally:

  14. conn.commit()

  15. cursor.close()

  16. conn.close()

  17.  
  18. # 執行sql

  19. with mysql() as cursor:

  20. # 左連線查詢

  21. r = cursor.execute("select * from users as u left join articles as a on u.id = a.user_id where a.user_id = 2")

  22. result = cursor.fetchall()

  23. print(result)

  24.  

檢視執行結果:

[{'title': '星光大道', 'username': 'tangtang', 'user_id': 2, 'email': '[email protected]', 'a.id': 2, 'content': '成名之路', 'password': '456', 'id': 2}]