要求:
上代码:
服务端:
import redis
import re
import json
import time
import cgi
from redis import StrictRedis, ConnectionPool
from flask import Flask,jsonify,request
import requests
app = Flask(__name__)
def insert_into_redis(shorturl,longurl):
print("----come to function--- insert_into_redis(shorturl,longurl)")
# 如果含义为插入,则做插入操作
pool = ConnectionPool(host='localhost', port=6379, db=0, decode_responses=True)
# redis 取出的结果默认是字节,我们可以设定 decode_responses=True 改成字符串。
r = StrictRedis(connection_pool=pool)
string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
r.hset("shorttolong",shorturl, json.dumps({"longurl": longurl,"visittime": [], "creattime":string}))
r.hset("longtoshort",longurl,shorturl)
print("Info: The value {0} is inserted".format(shorturl))
return 1
def check_if_exist(longurl):
print("----come to function--- check_if_exist(longurl)")
pool = ConnectionPool(host='localhost', port=6379, db=0, decode_responses=True)
r = StrictRedis(connection_pool=pool)
# 判断是否存在,如果存在则返回1
if r.hexists("longtoshort",longurl):
result = 1
else:
result = 0
return result
def check_shorturl_if_exist(shorturl):
print("----come to function--- check_shorturl_if_exist(shorturl)")
pool = ConnectionPool(host='localhost', port=6379, db=0, decode_responses=True)
r = StrictRedis(connection_pool=pool)
# 判断是否存在,如果存在则返回1
if r.hexists("shorttolong",shorturl):
result = 1
else:
result = 0
return result
def get_longurl(shorturl):
print("----come to function--- get_longurl(shorturl)")
pool = ConnectionPool(host='localhost', port=6379, db=0, decode_responses=True)
r = StrictRedis(connection_pool=pool)
# 判断是否存在,如果存在则返回1
longurljson = r.hmget("shorttolong",shorturl)
longurl = json.loads(longurljson[0])["longurl"]
#print(longurljson)
#print(longurl)
return longurl
def update_jumptime(shorturl):
print("----come to function--- update_jumptime(shorturl)")
pool = ConnectionPool(host='localhost', port=6379, db=0, decode_responses=True)
r = StrictRedis(connection_pool=pool)
longurljson = r.hmget("shorttolong", shorturl)
dic1 = json.loads(longurljson[0])
list1 = dic1["visittime"]
#print(list1)
string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
list1.append(string)
dic1["visittime"] = list1
r.hset("shorttolong", shorturl, json.dumps(dic1))
print("info: update the visittime of the {0} success".format(shorturl))
return 1
"""
0.判断长链接是否已经存在
在用户端进行创建的时候,要检查长链接是否已经在服务端存在了,如果存在应该忽略。
如何确定服务端存在,通过post 接口来查询。
检查长URL是否有效:通过checkurl(longurl)函数,当longurl 已经存在,
返回json字段{'result':1},不存在则否则返回{'result':0}
"""
@app.route('/api/checkurl', methods=['POST'])
def check_url():
print("----come to function--- check_url() /api/checkurl logic")
longurl = request.json['longurl']
result = check_if_exist(longurl)
if result == 0:
print("Info: The longurl {0} is not exist at serverside storage".format(longurl))
else:
print("Info: The longurl {0} is already exist at serverside storage".format(longurl))
return jsonify({'result':result,"longurl":longurl}),200
@app.route('/api/checkshorturl', methods=['POST'])
def check_short_url():
print("----come to function--- check_short_url() /api/check_short_url logic")
shorturl = request.json['shorturl']
result = check_shorturl_if_exist(shorturl)
if result == 0:
print("Info: The shorturl {0} is not exist at serverside storage".format(shorturl))
else:
print("Info: The shorturl {0} is already exist at serverside storage".format(shorturl))
return jsonify({'result':result,"shorturl":shorturl}),200
"""
1.收集长链接生成短链接:
根据客户端的post 请求,收集客户端发送的shorturl以及longurl,把关联关系插入到redis中
插入的时候,redis表中有两个hash库,
"shorttolong" 库,结构为 shorturl:{"longurl":longurl,"visittime",{},"createtime":string}
"longtoshort" 库,结构为 longurl:shorturl
"""
@app.route('/api/shorturlcreate',methods=['POST'])
def shorturl_create():
print("----come to function---shorturl_create() /api/shorturlcreate logic")
shorturl = request.json['shorturl']
longurl = request.json['longurl']
insert_into_redis(shorturl, longurl)
print("info: insert into redis {0}:{1} pair success".format(shorturl,longurl))
return jsonify({"info":"insert into redis "+longurl+shorturl+" pair succuss"})
"""
2.收集短链接做相关跳转:
客户端发出短链接请求,
2-1: 判断短链接是否存在
2-2: 如果存在,则找到对应的长链接
2-3: 返回301 指令,并让客户端做跳转
"""
@app.route('/api/shorturljump',methods=['POST'])
def shorturl_jump():
print("----come to function---shorturl_jump() /api/shorturljump logic")
shorturl = request.json['shorturl']
if check_shorturl_if_exist(shorturl) == 1:
# getlongurl mock
longurl=get_longurl(shorturl)
# 增加一个跳转的时间,对他记录。
# redis_update_jumptime(shorturl) mock
update_jumptime(shorturl)
# jumpto destination longurl,mock
print("info: jump to destination longurl {0} ".format(longurl))
#redirect_to_longurl(longurl)
return jsonify({"info": "jump to destionation","longurl":longurl})
else:
return jsonify({"info": "the site {0} is not exist".format(shorturl),"longurl":"notexist"})
if __name__ == '__main__':
app.run(host='0.0.0.0',port=8008,debug = True)
客户端:
from flask import Flask,render_template,request,url_for,redirect
from datetime import date
import requests
import json
import time
def check_if_valid(longurl):
#检查长链接是否有效,这里mock一下
print("----come to function--- check_if_valid(longurl)")
try:
r = requests.get("https://"+longurl)
statuscode = r.status_code
if statuscode == 200 or statuscode == 301:
print("The site is reachable on internet")
result = 1
else:
result = 0
except:
result = 0
return result
def check_if_exist_url(longurl):
print("----come to function--- check_if_exist_url(longurl)")
#检查长链接是否在服务端存在,mock一下
print("Process: check if the longurl {0} is exist at serverside redis storage".format(longurl))
r = requests.post("http://127.0.0.1:8008/api/checkurl", json={"longurl": longurl})
textjson = r.json()
print("get the return info from the serverside {0}".format(longurl))
print(textjson)
print("get the type info of the serverside")
print(type(textjson))
print("Get the longurl ")
print(textjson["longurl"])
#print(dic2["longurl"])
result = textjson["result"]
return result
def post_shorturlcreate(shorturl,longurl):
#模拟postman,传递数据到服务端。
print("----come to function--- post_shorturlcreate(shorturl,longurl)")
print("Process: to deliver create link to serverside redis storage")
r = requests.post("http://127.0.0.1:8008/api/shorturlcreate",json={"shorturl":shorturl,"longurl":longurl})
print("get info from serverside \n"+ r.text)
return 1
def post_shorturljump(shorturl):
print("----come to function--- post_shorturljump(shorturl)")
print("Process: jump to shorturl")
r = requests.post("http://127.0.0.1:8008/api/shorturljump", json={"shorturl": shorturl})
print("get info from serverside \n" + r.text)
return r
def create_shorturl(longurl):
print("----come to function--- create_shorturl(longurl)")
print("Process: to create shorturl from longurl")
#返回shorturl
shorturl = "/"+longurl.split(".")[1]
print("Process:The short url is "+shorturl)
return shorturl
def check_shorturl_if_exist(shorturl):
print("----come to function--- check_shorturl_if_exist()")
print("Process: check if the shorturl {0} is exist at serverside redis storage".format(shorturl))
r = requests.post("http://127.0.0.1:8008/api/checkshorturl", json={"shorturl": shorturl})
textjson = r.json()
print("Print the info return from serverside,this is the info")
print(textjson)
print("Check the type of the info")
print(type(textjson))
print("Check the mapping(longurl) of the shorturl {0}".format(shorturl))
print(textjson["shorturl"])
# print(dic2["longurl"])
result = textjson["result"]
return result
app = Flask(__name__)
@app.route('/',methods=['GET','POST'])
def index():
if request.method == 'POST': #根据post 表单获取的内容做相关判断
longurl = request.form['longurl']
shorturl = request.form['shorturl']
print("longurl is {0}, shorturl is {1}".format(longurl,shorturl))
if longurl is not None and shorturl == "empty": #当longurl 非空
print("进入第一个逻辑")
if check_if_valid(longurl) == 1 and check_if_exist_url(longurl) == 0: #当longurl 可用,且longurl在服务端不存在
shorturl = create_shorturl(longurl)
post_shorturlcreate(shorturl, longurl)
notes = "the longurl {0} and shorturl {1} pair is created".format(longurl,shorturl)
return render_template('display.html', info=notes)
else: #否则条件没达到,通知失败
notes = "the longurl is not exist or it's already at serverside"
return render_template('display.html', info=notes)
if shorturl is not None and longurl == "empty": #当shorturl 非空,执行第二个逻辑
print("进入第二个逻辑")
if check_shorturl_if_exist(shorturl) == 1:# 如果短url在服务端存在,则做跳转等逻辑
r = post_shorturljump(shorturl)
print(r.json())
print(type(r.json()))
longurl = r.json()["longurl"]
print(longurl)
return redirect("https://" + longurl)
else:
notes = "the shorturl is not exist"
return render_template('display.html', info=notes)
if shorturl is not None and longurl == "statics": #当shorturl 非空,且longurl为统计,执行第三个逻辑
print("进入第三个逻辑")
visittime = []
if check_shorturl_if_exist(shorturl) == 1:# 如果短url在服务端存在,则收集它的访问时间并存到字典
visittime =
else:
notes = "the shorturl is not exist"
return render_template('display.html', info=notes)
else:
return render_template('index5.html')
@app.route('/api/static',methods=['GET','POST'])
def get_static():
print("进入第三个逻辑")
print("统计某个短链的访问情况")
return render_template('display.html', info="The site info is displayed")
if __name__ == '__main__':
app.run(host='0.0.0.0',port=80,debug = True)
index5.html
<!DOCTYPE html>
<html lang="en">
<script type="text/javascript">
if(form.longurl.value ==''){
form.longurl.value == "empty"
}
if(form.shorturl.value ==''){
form.shorturl.value == "empty"
}
</script>
<head>
<meta charset="UTF-8">
<title>57-54</title>
</head>
<body>
<form method = "post">
<label for="longurl">提供想生成短链接的url</label>
<input type="text" name="longurl" required><br>
<input type="hidden" name="shorturl" value ="empty"><br>
<input type="submit" name="submit" value="记录">
</form>
<form>
<label for="shorturl">提供想跳转的url</label>
<input type="hidden" name="longurl" value="empty"><br>
<input type="text" name="shorturl" required><br>
<input type="submit" name="submit" value="跳转">
</form>
</body>
</html>
display.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>57-53</title>
</head>
<body>
{{info}}
</body>
</html>
前端
键入www.qq.com
点击记录
实现了插入逻辑:
插入逻辑前端日志:
插入逻辑后端日志:
查看数据库:
跳转前逻辑:
键入qq,点击跳转
跳转后:
前端日志:
后端日志:
看访问做了跳转302,
update后,跳转时间做了记录
url脑图
最常用的是直接请求,默认是GET方法
curl <https://baidu.com>
添加header使用 -H, --header
curl -H "X-MY-HEADER: abc" <https://baidu.com>
如果想添加多个header,那么就写多次
curl -H "X-MY-HEADER: abc" -H "X-MY-HEADER2: def" <https://baidu.com>
有些时候想要post或者put请求,可以使用 -X, --request <method> 来自设置
curl -X POST <https://baidu.com>
当post的时候想要传输body参数,可以使用 -d, --data <data> 来设置
curl -X POST -d "name=abc&gender=0" <https://example.com>
或者
curl -X POST -d name=bac -d gender=0 <https://example.com>
需要添加cookie来请求,可以使用 -b, --cookie 来设置
curl -b "token=abcdef;uid=123123" <https://example.com>
那如果想要把返回的cookie存储起来呢,可以用 -c, --cookie-jar <file> 来设置存储的位置
curl -c cookie.txt <https://www.baidu.com>
需要把请求的返回结果输出到文件,以便查看分析,可以用 -o, --output <file> 来设置输出到的文件
curl -o baidu.html <https://www.baidu.com>
示例如下图,会打印出整体进度和统计
curl被人熟知的是系统自带的用来请求HTTP url的工具。但是,其不但可以处理http协议,还可以处理:FILE, FTP, FTPS, GOPHER, IMAP, IMAPS, LDAP, LDAPS, POP3, POP3S, RTMP, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET and TFTP等。。。这么多协议,怎样,功能是不是很强大。
curl --mail-from "abc@def.com" --mail-rcpt "12345566@qq.com" --upload-file mail.txt --user "user_name:password" --url "smtp://mail.qq.com"
curl -T "img[1-1000].png" <ftp://ftp.example.com/upload/>
或者
curl --upload-file "{file1,file2}" <http://www.example.com>
最简单的使用方式是:curl <https://baidu.com> ,但是偶尔会有想同时请求多个url,能不能办的到呢,当然可以
curl https://{baidu,cnblogs}.com
这样的话就会顺序请求 https://baidu.com、https://cnblogs.com 这两个网站。(PS: 终端里显示{ 或}之前有\,是终端做的转义功能,请忽略。)
又有说请求的url不一定都是.com结尾呢,能不能办呢,那必须可以
curl https://{baidu.com,csdn.net}
又有人较真说,我请求的多个url协议都是不同的,比如:curl {<http://baidu.com>, <https://csdn.net>} , 能不能这么请求呢,那不好意思,这样不行,会解析错误。
这种用法多用于某个站点多种path或query的情况,比如
可以使用 -x,--proxy [protocol://]host[:port] 参数 代理来请求目标网站
curl --proxy <http://127.0.0.1:1087> <https://baidu.com>
使用http的代理是可以来请求https目标网站的,其中原理是使用了http的proxy tunnel功能,这个在后续文章中会做详细介绍。
有些时候想要知道详细的请求情况,比如怎么建立连接的,请求过程是如何的,那么可以这么来用,使用 -v ,--verbose
curl -v <https://baidu.com>
有了上面的详细请求可能还不太满足一些需求,比如想要知道花费建立连接时间、传输时间、相应时间等详细的性能信息,这个对于最终网络问题很有帮助,那么怎么办呢。
那就要拿出一个厉害的参数:-w, —write-out
比如下面的例子可以输出:发起链接时间,开始传输时间以及总花费时间。
curl -w 'time_connect %{time_connect}s\ntime_starttransfer %{time_starttransfer}s\ntime_total %{time_total}s\n' <https://baidu.com>
可以来一个更详细的追踪
curl -w 'http_code: %{http_code}\ncontent_type: %{content_type}\ntime_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_appconnect: %{time_appconnect}\ntime_redirect: %{time_redirect}\ntime_pretransfer: %{time_pretransfer}\ntime_starttransfer: %{time_starttransfer}\nspeed_download: %{speed_download}\nspeed_upload: %{speed_upload}\nsize_download: %{size_download}\nsize_upload: %{size_upload}\n---------\n time_total: %{time_total}\n' <https://baidu.com>
结果如下图
-w 可以使用的变量比较多,常用的如下表所示:
可以设置失败请求重试次数,以及最大重试次数、超时时间等。
例如,设置重复次数请求一个不存在的url
curl --retry 3 --retry-delay 1 --retry-max-time 10 <https://notexisturl.com>
例如,设置超时时间来请求
curl -m 1 <https://baidu.com>
pring Security 系列继续。
前面的视频+文章,松哥和大家简单聊了 Spring Security 的基本用法,并且我们一起自定义了一个登录页面,让登录看起来更炫一些!
今天我们来继续深入这个表单配置,挖掘一下这里边常见的其他配置。学习本文,强烈建议大家看一下前置知识(松哥手把手带你入门 Spring Security,别再问密码怎么解密了),学习效果更佳。
很多初学者分不清登录接口和登录页面,这个我也很郁闷。我还是在这里稍微说一下。
登录页面就是你看到的浏览器展示出来的页面,像下面这个:
登录接口则是提交登录数据的地方,就是登录页面里边的 form 表单的 action 属性对应的值。
在 Spring Security 中,如果我们不做任何配置,默认的登录页面和登录接口的地址都是 /login,也就是说,默认会存在如下两个请求:
如果是 GET 请求表示你想访问登录页面,如果是 POST 请求,表示你想提交登录数据。
在上篇文章中,我们在 SecurityConfig 中自定定义了登录页面地址,如下:
.and()
.formLogin()
.loginPage("/login.html")
.permitAll()
.and()
当我们配置了 loginPage 为 /login.html 之后,这个配置从字面上理解,就是设置登录页面的地址为 /login.html。
实际上它还有一个隐藏的操作,就是登录接口地址也设置成 /login.html 了。换句话说,新的登录页面和登录接口地址都是 /login.html,现在存在如下两个请求:
前面的 GET 请求用来获取登录页面,后面的 POST 请求用来提交登录数据。
有的小伙伴会感到奇怪?为什么登录页面和登录接口不能分开配置呢?
其实是可以分开配置的!
在 SecurityConfig 中,我们可以通过 loginProcessingUrl 方法来指定登录接口地址,如下:
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/doLogin")
.permitAll()
.and()
这样配置之后,登录页面地址和登录接口地址就分开了,各是各的。
此时我们还需要修改登录页面里边的 action 属性,改为 /doLogin,如下:
<form action="/doLogin" method="post">
<!--省略-->
</form>
此时,启动项目重新进行登录,我们发现依然可以登录成功。
那么为什么默认情况下两个配置地址是一样的呢?
我们知道,form 表单的相关配置在 FormLoginConfigurer 中,该类继承自 AbstractAuthenticationFilterConfigurer ,所以当 FormLoginConfigurer 初始化的时候,AbstractAuthenticationFilterConfigurer 也会初始化,在 AbstractAuthenticationFilterConfigurer 的构造方法中,我们可以看到:
protected AbstractAuthenticationFilterConfigurer() {
setLoginPage("/login");
}
这就是配置默认的 loginPage 为 /login。
另一方面,FormLoginConfigurer 的初始化方法 init 方法中也调用了父类的 init 方法:
public void init(H http) throws Exception {
super.init(http);
initDefaultLoginFilter(http);
}
而在父类的 init 方法中,又调用了 updateAuthenticationDefaults,我们来看下这个方法:
protected final void updateAuthenticationDefaults() {
if (loginProcessingUrl == null) {
loginProcessingUrl(loginPage);
}
//省略
}
从这个方法的逻辑中我们就可以看到,如果用户没有给 loginProcessingUrl 设置值的话,默认就使用 loginPage 作为 loginProcessingUrl。
而如果用户配置了 loginPage,在配置完 loginPage 之后,updateAuthenticationDefaults 方法还是会被调用,此时如果没有配置 loginProcessingUrl,则使用新配置的 loginPage 作为 loginProcessingUrl。
好了,看到这里,相信小伙伴就明白了为什么一开始的登录接口和登录页面地址一样了。
说完登录接口,我们再来说登录参数。
在上篇文章中,我们的登录表单中的参数是 username 和 password,注意,默认情况下,这个不能变:
<form action="/login.html" method="post">
<input type="text" name="username" id="name">
<input type="password" name="password" id="pass">
<button type="submit">
<span>登录</span>
</button>
</form>
那么为什么是这样呢?
还是回到 FormLoginConfigurer 类中,在它的构造方法中,我们可以看到有两个配置用户名密码的方法:
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
在这里,首先 super 调用了父类的构造方法,传入了 UsernamePasswordAuthenticationFilter 实例,该实例将被赋值给父类的 authFilter 属性。
接下来 usernameParameter 方法如下:
public FormLoginConfigurer<H> usernameParameter(String usernameParameter) {
getAuthenticationFilter().setUsernameParameter(usernameParameter);
return this;
}
getAuthenticationFilter 实际上是父类的方法,在这个方法中返回了 authFilter 属性,也就是一开始设置的 UsernamePasswordAuthenticationFilter 实例,然后调用该实例的 setUsernameParameter 方法去设置登录用户名的参数:
public void setUsernameParameter(String usernameParameter) {
this.usernameParameter = usernameParameter;
}
这里的设置有什么用呢?当登录请求从浏览器来到服务端之后,我们要从请求的 HttpServletRequest 中取出来用户的登录用户名和登录密码,怎么取呢?还是在 UsernamePasswordAuthenticationFilter 类中,有如下两个方法:
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
可以看到,这个时候,就用到默认配置的 username 和 password 了。
当然,这两个参数我们也可以自己配置,自己配置方式如下:
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/doLogin")
.usernameParameter("name")
.passwordParameter("passwd")
.permitAll()
.and()
配置完成后,也要修改一下前端页面:
<form action="/doLogin" method="post">
<div class="input">
<label for="name">用户名</label>
<input type="text" name="name" id="name">
<span class="spin"></span>
</div>
<div class="input">
<label for="pass">密码</label>
<input type="password" name="passwd" id="pass">
<span class="spin"></span>
</div>
<div class="button login">
<button type="submit">
<span>登录</span>
<i class="fa fa-check"></i>
</button>
</div>
</form>
注意修改 input 的 name 属性值和服务端的对应。
配置完成后,重启进行登录测试。
在登录成功之后,我们就要分情况处理了,大体上来说,无非就是分为两种情况:
两种情况的处理方式不一样。本文我们先来卡第二种前后端不分的登录,前后端分离的登录回调我在下篇文章中再来和大家细说。
在 Spring Security 中,和登录成功重定向 URL 相关的方法有两个:
这两个咋看没什么区别,实际上内藏乾坤。
首先我们在配置的时候,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可,具体配置哪个,则要看你的需求,两个的区别如下:
相关配置如下:
.and()
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/doLogin")
.usernameParameter("name")
.passwordParameter("passwd")
.defaultSuccessUrl("/index")
.successForwardUrl("/index")
.permitAll()
.and()
「注意:实际操作中,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可。」
与登录成功相似,登录失败也是有两个方法:
「这两个方法在设置的时候也是设置一个即可」。failureForwardUrl 是登录失败之后会发生服务端跳转,failureUrl 则在登录失败之后,会发生重定向。
注销登录的默认接口是 /logout,我们也可以配置。
.and()
.logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout","POST"))
.logoutSuccessUrl("/index")
.deleteCookies()
.clearAuthentication(true)
.invalidateHttpSession(true)
.permitAll()
.and()
注销登录的配置我来说一下:
好了,今天就先说这么多,这块还剩一些前后端分离交互的,松哥在下篇文章再来和大家细说。
「如果感觉有收获,记得点一下右下角在看哦」
*请认真填写需求信息,我们会在24小时内与您取得联系。