Ddragon's blog

coding


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

Java servlet

发表于 2018-09-10   |  

容器和Java web

一、java web容器规范

1、概念
  • web容器是能部署web应用程序的环境
  • Tomcat容器是Java Servlet,JavaServer Pages,Java Expression Language和Java WebSocket(Java EE)技术实现
  • Servlet是主要作用是给上级容器(Tomcat)等提供doGet()和doPost()等方法。其生命周期实例化、初始化、调用、销毁受控于Tomcat容器
  • Java Web应用由一组Servlet、HTML页、类、以及其它可以被绑定的资源构成。它可以在各种web容器(如Tomcat)中运行。

二、Servlet

  • Servlet本质上市一个java类,按照规范可以在javaweb容器中运行,对客户端的请求做出响应。
2.1、Servlet的生命周期
  • init()初始化,数据初始化
  • service()方法处理请求调用的实际方法,请求后把结果返回给客户端,service() 方法由容器调用,service 方法在适当的时候调用 doGet、doPost、doPut、doDelete 等方法。
  • destory()方法结束请求
1
2
3
4
5
6
7
8
9
10
11
    //service方法调用特征
public void service(ServletRequest request,
ServletResponse response)
throws ServletException, IOException{
}
//调用service之后常见的doGet doPost方法
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 处理
}
2.2、Servlet demo
  • 流程

    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
        import java.io.*;
    import javax.servlet.*;
    import javax.servlet.http.*;
    public class SampileServlet extends HttpServlet {

    private String message;
    public void init() throws ServletException
    {
    // 执行必需的初始化
    message = " sample";
    }
    public void doGet(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException
    {
    // 设置响应内容类型
    response.setContentType("text/html");
    // 实际的逻辑是在这里
    PrintWriter out = response.getWriter();
    out.println("<h1>" + message + "</h1>");
    }

    public void destroy()
    {
    }
    }
  • 在路由层配置url映射关系,类似于spring中的RequestMapping,Python flaskweb框架中的@app.route(“/index.html”)功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14

<web-app>
<servlet>
<description></description>
<display-name>HelloServlet</display-name>
<servlet-name>HelloServlet</servlet-name>
<jsp-file>/servlet.jsp</jsp-file>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/servlet/HelloServlet</url-pattern>
</servlet-mapping>
<servlet>
</web-app>

三、Cookie和Session

  • Session保存在服务端,主要是服务端了标识用户而产生的,用于会话跟踪。
  • Cookie,cookie是客户端发送给服务端的,session的跟踪主要也是用cookie来实现的。cookie信息一般保存在http报文信息中。
  • 在jsp中,有Cookie对象供用户调用,可以设置服务端需要的key和value
1
Cookie cookie = new Cookie("key","value");

四、JSP

  • Java server pages一种根据服务端数据动态生成界面的技术,和很多前端模板引擎类似,jsp是一个servle程序。在界面中写入很多控制代码,来实现数据的动态渲染,之前学习过jinja2类似的模板引起,语法非常像。

反射注解与动态代理

发表于 2018-09-10   |  

反射,注解和动态代理,类加载

一、反射

1、概念
  • java的反射机制指的是程序在运行过程中,对于类,能获取到类的属性和方法,对于对象,能够调用他的属性和方法,动态的获取数据。
1、获取类的属性和方法
  • 假设有一个Student类,
1
2
3
4
5
6
7
8
9
10
/**
* class
*/
public class Student {
public String name;

public void doHomeWork(){
//do something
}
}
  • 获取类名和属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class Main {

public static void main(String[] args) {
Class stuClass = Student.class;
System.out.println(stuClass.getName());
Field[] fields = stuClass.getFields();
Method[] methods = stuClass.getMethods();
for(Field field : fields){
System.out.println(field.getName());
}
for(Method method : methods){
System.out.println(method.getName());
}
}
}
  • 此外,也可以访问私有方法,通过 class.getDeclaredMethod(“privateMethod”, String.class, int.class)可以获取。

二、注解

  • 注解可以使得Java源代码中可以添加功能性的实现代码,还可以添加元数据,和类于接口一样,注解也属于一种类型,使用@interface进行标记,比如定义一个Hello注解
1
2
3
4
5
6
7
8
9
10
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Hello {

}
  • 使用场景,当类,属性和对象标记注解以后,注解的信息提取使用java反射的形式,主要给编译器以及工具类型的软件使用。
2.1 元注解
  • 元注解是标记注解的注解,元注解可以指明注解是做什么的,常用的有:
  • @Retention 注解的保留期,取值有RetentionPolicy.SOURCE(注解在源码期间保留), RetentionPolicy.CLASS(保留到编译进行的时候,不会加载进jvm) RetentionPolicy.RUNTIME(保留到程序运行时)
  • @Document 将注解的元素包含到java doc中
  • @Target 注解使用的地方,可以指定类,属性或者方法等,有较多选择
  • @Inherited 继承功能,当超类使用了这个注解,他的子类也会使用上这个注解。
  • @Repeatable 可重复

三、动态代理

  • 代理模式,中间人模式,常见的场景是用户要做一件事情,不会自己亲自做,而是会让中介去做,帮自己实现,面向对象程序设计中很常见的设计模式。代理流程如下:
  • 动态代理可以用来做方法的功能增强,对代码无侵入,动态代理使用Proxy的静态方法newProxyInstance动态创建代理,如下
1
2
3
public static Object newProxyInstance(ClassLoader loader,//类加载器
Class<?>[] interfaces, //代理接口
InvocationHandler h)//关联接口
  • 要创建动态代理,核心是要实现InvocationHandler接口和invoke方法,比如,要为一个接口的功能实现进行日志打印,相关流程:
1
2
3
4
5
//动态代理只能为接口创建代理
interface Work{
void getInfo();
void doWork();
}
1
2
3
4
5
6
7
8
9
10
11
12
public class HomeWork implements Work{

@Override
public void getInfo(){
System.out.println("收集资料");
}

@Override
public void doWork(){
System.out.println("正式工作");
}
}
1
2
3
4
5
6
   //增强功能
public class WorkLog {
public void log(){
System.out.println("工作记录......");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class WorkInvocationHandler implements InvocationHandler {

private Object target;

public void setTarget(Object target){
this.target = target;
}

//注意抛出异常,实现invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
WorkLog workLog = new WorkLog();
workLog.log();

return method.invoke(target, args);

}
}
1
2
3
4
5
6
7
8
9
10
11
12
    import java.lang.reflect.Proxy;

class MyProxyFactory{

public static Object getProxy(Object target) throws Exception{
// 实例化对象
WorkInvocationHandler handler = new WorkInvocationHandler();
handler.setTarget(target);
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),handler);
}
}
1
2
3
4
5
6
7
8
9
10
    public static void main(String[] args) {
// 实例化一个被代理对象
try {
Work target = new HomeWork();
Work work = (Work) MyProxyFactory.getProxy(target);
work.getInfo();
work.doWork();
}catch (Exception ignored){}

}

四、类加载机制

1、概述
  • Java代码需要运行,首先需要将源代码进行编译生成 .class 文件,然后 JVM 加载 .class 字节码文件到内存,而 .class 文件是怎样被加载到 JVM 中的就是Java 类加载
2、分类
  • AppClassLoader 应用加载器,也称为系统加载器,会加载来自CLASSPATH中的类和包,默认情况下,其它类加载器会将它作为父类加载器
  • ExtClassloader 拓展类加载器,主要加载Java的拓展类库
  • BootStrapClassLoader 启动类加载器,是最顶层的父类加载器,主要用来加载java的核心类
3、加载过程
  • 加载流程如下:
    • 加载过程采用双亲委派模型,当一个类加载器收到类加载任务时,会有限交给父类处理,父类无法完成时才会尝试自行加载任务。保证了使用不同的类加载器最终得到的都是同一个对象。

Java线程

发表于 2018-09-10   |  

多线程

一、概念

1、进程和线程的概念

  • 进程和线程都是操作系统中的概念,操作系统对进程和线程的严格定义是:进程是具有一定独立功能的程序,是系统进行资源分配和调度的独立单位。线程是进程的实体,是cpu调度和分配的基本单位。

2、进程与线程的关系

  • 一个线程只能属于一个进程,而一个进程可以有多个线程。
  • 同一进程的所有线程共享该进程的资源。
  • 线程在执行过程中,不同进程的线程间要利用消息通信的办法实现数据同步。

3、线程的生命周期

  • 主要有创建,就绪,运行,阻塞,销毁几个状态,如图:

2、java与多线程

  • java为了提升程序性能,也引入了多线程概念,通过多线程,在一些场景能极大提升性能,实现线程主要有两种方式,一种继承Thread类,另一种实现Runable接口,
    2.1 继承Thread类型
  • 创建线程,可以通过继承Thread类并实现run()方法的形式,run()里面编写实际要运行的代码,运行时通过start()方法将线程设为可运行态,并不是直接执行,具体调用还是要操作系统决定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    /**
* 继承Thread
*/
public class TestThread extends Thread{

private String threadName;

public TestThread(String name){
this.threadName=name;
}

@Override
public void run(){
System.out.print(threadName);
}
}
2.2 实现Runnable接口
  • 线程类通过实现Runnable接口,实现run()方法,也能实现线程调用,和继承Thread类类似,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class RunnableThread implements Runnable{

private String threadName;

public RunnableThread(String name){
this.threadName=name;
}

@Override
public void run(){
System.out.println(threadName);
}
}
2.3 详细的启动过程
  • 1、通过new创建一个线程对象
  • 2、调用线程对象的start()方法,使线程处理Runnable可运行状态,等待调用
  • 3、当被调用时,执行run()中的代码
  • 4、当线程因为原因在运行时放弃cpu调用时(比如被优先级高的线程抢占),便会进入阻塞状态,当要继续运行时,需要重新进入可运行状态。
  • 5、执行完成,会进入销毁状态。

3、线程同步

  • 同一个进程的线程资源是共享的,当操作共享资源时,会存在问题,为了避免临界区造成的问题,线程之间需要进行同步,主要有下面几种同步方式
3.1、使用synchronized进行修饰
  • 在需要同步的方法面前加入synchronized进行修饰,synchronized会将对象锁住,能够保证在同一时刻只有一个线程访问,在调用方法之前,需要先获得内置锁,同时,除了修饰方法,还能修饰代码块
1
public void synchronized saveMoney(){//todo somethind};
1
synchronized(object){}; //修饰对象
3.2、使用Lock锁
  • 锁机制也能保证同一个时刻只能有一个线程进行访问,在在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReentrantLock可重入锁等
1
2
3
4
5
6
7
     Lock l = ...; //lock实现对象对象
l.lock();
try {
//todo something
} finally {
l.unlock();
}
3.3 volatile
  • volatile主要保证了一个共享变量在多个线程访问的情况下,保证了共享变量的可见性,比如一个线程对变量进行了修改,另一个线程能同步到修改的值。

4、线程池

  • 在单个线程的使用中,频繁的创建销毁线程对象损耗较多的时间,影响系统性能,当有任务来时,临时创建性能也非常低效,节省了时间和效率。所以在多线程的开发中,尽量使用线程池的形式。
4.1、ThreadPoolExecutor
  • 使用ThreadPoolExecutor进行创建线程池,用法
1
2
3
4
5
6
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
}
  • corePoolSize是核心池的大小,maximumPoolSize最大线程数,keepAliveTime当没有任务时线程的存活时间,weekQueue阻塞队列,用来保存执行的任务,unit时间单位
  • 协作流程:线程优先向 CorePool 中提交;在 Corepool 满了之后,线程被提交到任务队列,等待线程池空闲;在任务队列满了之后 corePool 还没有空闲,那么任务将被提交到 maxPool 中,如果 MaxPool 满了之后执行 task 拒绝策略。

Java集合框架

发表于 2018-09-10   |  

Java集合框架

1、概述

  • java除了提供基本的数据类型支持外,还提供了对数据对象类型的存储框架,可以非常高效的,方便的操纵这些数据对象,并且能动态增长,集合可以用来存储、检索、操作、通信,它主要包含接口和实现类一些具体的操作方法。
  • java的集合框架主要有两种,一种是集合(Collection),集合是存储一类元素的结合,它是最基本的容器接口,继承了Iterable接口。另一种是图,它存储了键值对的映射关系,通过键能得到值。

2、Collection

  • Collection接口有三种子类型,分别为List,Set和Queue,由抽象类到具体的实现类,常用的有

    1、List介绍

  • List容器中的元素是有序可重复的,它能够控制每个元素的插入位置,用户还可以直接通过索引来进行访问。List的实现类还有LindedList,ArrayList,vector和stack.

    2、Set介绍

  • Set里面存储的元素都是不重复的,并且里面的元素是无序的

3、Queue介绍

  • 队列继承了Collection接口,和List,Set是同一个级别的,只允许在一端进行插入操作,另一端进行删除操作。具有先进先出的特性

4、Map

  • 在程序中用于管理映射关系
  • map里面的key和value是一一对应的,并且不允许里面的key重复
  • map里面最重要的有一个entry方法,里面定义了对key或者value的获取方法
4.1 HashMap
  • HashMap是最常用的map,存储根据键的hashCode来进行存储,根据键可以直接获取值,访问速度很快。
  • 遍历方式:遍历方式有根据keySet()值遍历和entrySet()键值进行遍历,还有迭代器iterator()的遍历方式。
  • iterator()方法的遍历:
1
2
3
4
5
6
7
8
9
10
11
12
# 使用keySet进行遍历
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(map.get(key));
}
# 使用entrySet()进行遍历
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println(entry.getValue());
}

3、Iterator

  • iterator其实就是遍历,提供的标准化遍历容器内所有对象的方法,在迭代过程中,使用者不需要了解集合的内部的结构。
  • iterator主要有3个方法
1
2
3
hasNext()  //判断是否还有元素供迭代
next() //返回迭代的下一个元素
remove() //移除迭代器返回的最后一个元素

4、泛型

  • 泛型主要是为了解决代码被不同对象的类型重用的问题,在集合框架中有广泛的使用。

4.1 泛型类

1
2
3
4
public class Pair<K, V> {
private K key;
private V value;
}

4.2 泛型方法

1
2
3
4
5
6
7
    public Class A{
public static <T>T test(T t){
return t;
}
public static <T> void hi(T t){
}
}

4.3 类型限定

  • 需要限制类型时使用 extends

Python函数式编程

发表于 2017-03-02   |   分类于 python   |  

1、Haskell到python函数式编程

    在学习python的过程中,一直体会着它的简洁和优雅,也钟情于它的简洁和优雅。但在一次偶然的机会中,在微博上看到某位博主贴出的一串Haskell代码,代码的透明,凝练,模块化,由小到大把我深深的吸引住,了解之余,发现Haskell是一门函数式编程语言,函数式编程语言有着深厚的数学知识作为基础,将电脑的运算看作是数学上的函数计算,可能这也是“数学之美”吧,在震惊之余,我也尝试了解,python是不是也支持函数式编程呢?结果是肯定的,Python作为一门命令式语言,但是也具备了函数式编程的特点。
    经过查询资料,不断学习,也慢慢的发现,函数式编程的确是很深奥,甚至于是一种晦涩难懂的编程模式,它将计算机中的运算看作是数学中函数的运算。掌握了可以让我们受益匪浅,也能体会到python一行代码实现强大功能的乐趣。

2、函数式编程里的数学知识

    函数式编程里面含有很多的数学知识,函数作为函数式编程里面的一等公民,它来源于数学里面函数,而数学中,递归,合成与柯里化,高级函数。这些函数的转化关系,也都体现在了函数式编程中。

3、函数式编程的特点

  • 函数就是数据,函数是一等公民
  • 纯函数:给一个值,一定返回另一个值,运算过程中部会被外部改变,具有透明性
  • 无状态,区别于命令式编程
  • 强编译器的支持(python不支持)

4、python函数式编程的实现

python里面,函数式编程主要通过一些高阶函数来体现,所谓高阶函数,就是可以接受其他函数作为参数的函数。

1、内置函数的实现

  • lambda 匿名函数

    lambda 在很多编程语言中都有出现,作为一个匿名函数,可以不用让我们不必花时间编写那些简单的函数,比如:

    1
    l = map(lambda x: x*x, xrange(1, 10))  # 对序列的每个数进行平方操作
  • map()

    map()本身接收一个函数和一个序列作为参数,然后将传入的函数依次作用在序列中的每个元素中,使用map可以对元素进行高效的处理迭代。比如:

    1
    2
    char_list = ['1', '2', '3']
    num_list = map(int, num_list)
  • reduce()

    reduce()接收一个函数(必须是二元操作函数)和一个序列作为参数,函数会作用于序列的元素,并且会把结果作用于下一个元素。比如:

    1
    2
    3
    4
    5
    l = [1, 2, 3]
    def add_exp(x, y):
    return x+y

    res = reduce(add_exp, l)
  • groupby()

    groupby()是itertools中的一个函数,它的作用是对一个序列进行聚合分组运算,接收一个函数和一个序列,函数作用于参数key

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from itertools import groupby

    l = [-1, 2, 5, 7, 13]
    def check_num(h):
    if h>10:
    return 'big'
    elif h<=10 and h>5:
    return 'middle'
    else:
    return 'small'
    res = groupby(l, key=check_num)
  • filter()

    filter()接收一个过滤函数和一个序列,对序列的每个元素,使用过滤函数对元素进行过滤,结果为除去过滤结果为True的元素

    1
    2
    3
    4
    5
    l = [1, 2, 3, 4]
    def is_prime(x):
    if x % 2 == 0:
    return True
    res = filter(is_prime, l)

2、语法的的实现

  • 列表生成式

    列表生成式是python一个非常强大的魔法,使用它可以用一行代码表达很多语义,比如:

    1
    [ x*x for x in xrange(10) if x % 2 ==0]  # 求10以内偶数的平方
  • 生成器generator

    生成器可以快速生成一个可迭代的对象,操作速度快。

    1
    2
    g_object = (x*x for x in xrange(5))  # xragne本身也是生成器
    g_object.next() # 使用next方法访问

3、第三方模块的的实现

  • pyrsistent 构建纯函数

    pysistent第三方库实现了python中一些不可变的数据类型,对于函数而言,使用不可变的数据类型,更加方便我们构造出纯函数
    
  • functools partial 偏函数

    partial的作用可以通过参数调用绑定的函数,设置默认的参数值

  • compose (高阶函数)

    当看到compose时, 我的第一反应是这不是和Redux框架中连接中间件的compose一样吗,事实也是这样,他们功能类型,通过compose,可以让函数绑定多个顺序函数。

  • toolz.functoolz curry

    用于实现函数柯里化,也就是多个单参数函数实现多参数函数的方法

  • fn模块 fn模块包含大量的易于构建函数式的语法糖

  • 递归优化(注意Python递归1000个调用限制)

python的编程美学

发表于 2017-02-18   |   分类于 python   |  

1、前言

写python的同学会经常听到pythonic这个词,但对于这个词语具体的语义,python社区每个人都有自己的独到的见解,因为pythonic自身就是人为造的单词。结合社区的观点可以知道:pythonic是python编程中的惯用方法,也就是写出简洁,优雅的代码,写出具有python独特风格的代码。

尝试运行下面这行代码,显示的结果便是python编程之禅

1
>>> import this

the zen of python

2、举个列子

大部分同学的入门语言都是c语言,这导致在写python的过程中,会不自觉地借用c语言的思想,写出类c语言风格的代码,比如在对一个数组遍历操作时, 我们使用c语言会经常这么写

1
2
3
4
int arr[4]={1, 2, 3, 4};
for (int i=0; i<4; i++){
do something(arr[i]);
}

于是,在使用python编写程序的时候,习惯性的会按照下面的方法写:

1
2
3
4
arr = [1, 2, 3, 4]
i = 0
for x in range(len(arr)):
do something(arr[x])

python以简洁优雅著称,上面的写法并没有很好的体现这一点,在python里面,任何可迭代的对象都能用for in来进行处理,更加优雅的实现方式是:

1
2
3
arr = [1, 2, 3, 4]
for element in arr:
do something(element)

编写更加pythonic的代码,不仅能让程序更加简洁优雅,还能提高程序的运行效率。下面介绍一些常用的更加pythonic的写法。

3、一些常见替代写法

  • (1)、 数字交换
1
2
3
a = 1
b = 2
a, b = b, a
  • (2)、使用字符串切片,对字符串快速操作
1
2
s = 'hello'
s_reverse = s[::-1] # 注意字符串切片的使用非常灵活
  • (3)、字符串拼接
1
2
str_arr   = ['hello', 'world']
res = ' '.join(str_arr) # 避免使用+拼接字符串,+操作会降低程序运行效率
  • (4)、列表生成式(快速生成有规律的列表)
1
2
3
4
for x in range(1, 5):
print x # 使用range构造了一个列表[1, 2, 3, 4]并迭代它,不用手动构造。当需要构造的list非常大时,使用xrange()会更好
num = [x for x in range(1, 5)] # 生成num = [1, 2, 3, 4]
num = [x for x in range(1, 5) if x%2 == 0] # 在上面的基础上过滤掉奇数
  • (5)、真值判断

    在python中,每种数据类型在空与非空的时候都有对应的真值与其匹配,掌握默认的真值能减 少冗余的代码:

True False
True False
非空字符串 空字符串
非0数字 数字0
非空容器 空的容器如[]{}
其它非False None
1
2
3
4
5
x = 'hello, world'
if x is not None:
pass
if x:
pass # 两种等效
  • (6)、使用for else语句快速跳出循环
1
2
3
4
5
6
for x in range(1,5):
if x == 3:
print 'find 3'
break
else:
print 'can not find 3!' # 可以检查循环是否跳出
  • (7)、zip(izip)迭代多个list
1
2
3
4
5
names = ['tom', 'alice', 'amy']
grades = [100, 90, 88]
for name, grade in zip(names, grades):
print name, grade # 同时迭代了两个list
d = dict(zip(names, grades)) # 使用zip快速构造字典
  • (8)、排序使用默认方法,不要自己造轮子
1
2
3
nums = [2, 1, 3, 0]
for num in sorted(nums):
print num # 对python而言,默认的排序方法sorted()方法效率非常高
  • (9)、字典默认值

在访问字典的时候,访问不存在的键值程序会报KeyError的操作。解决方式可以使用字典的get()方法,当然也可以使用默认字典

1
d = defautdict(int)
  • (10)、解构list
1
2
l = [1, 2, 3]
a, b , c = l # 避免使用a = l[0] 这种写法
  • (11)、同时迭代对象的值和索引
1
2
3
4
for x in range(1, 5):
do something(x) # 只能取出值
for index, x in enumerate(range(1, 5)):
print index, x # 使用enumerate()方法可以同时取出索引和值
  • (12)、最后:掌握PEP8(python增强建议)的建议写法,了解python的编程风格,这是最重要的。

4、参考资料

  • http://wuzhiwei.net/be_pythonic/
  • http://blog.csdn.net/gzlaiyonghao/article/details/2762251
  • https://www.youtube.com/watch?v=OSGv2VnC0go
  • http://blog.sae.sina.com.cn/archives/4781

了解跨域资源共享

发表于 2016-11-25   |  

1、背景

    之前在完成一个web项目的时候,采用了前后端分离的模式,后端采用了Flask搭建服务,前端基于React框架搭建界面。后台服务部署在了阿里云服务器,使用git来进行项目代码的更新,但是在前端通过Ajax调试后台接口的时候,却一直报错,对于第一次尝试前后分离的我们来说打击不小,最后对错误代码的分析,原来是跨域的问题。

1
2
//错误代码
**XMLHttpRequest cannot load api.jxnugo.com/course. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin '' is therefore not allowed access.**

2、什么是跨域

    要说跨域,首先要了解Ajax(Asynchronous JavaScript+XML),它的核心是XMLHttpRequest(XHR)对象,这个技术使得我们的界面无需刷新便可以异步地从服务器获得数据,从而更新DOM。

    跨域给开发者带来了很多的方便,同时也出现了一些弊端,XHR对象是实现Ajax通信的主要方式,而这样就带来了跨域安全的问题。在默认的情况下,XHR对象只能访问与包含它的页面处于同一个域中的资源,也就是在协议,域名,端口完全相同的情况中,是不会出现跨域的问题。这样做的目的是为了保证页面的安全性,防止界面受到外部资源的干扰,比如恶意代码的注入。但是不可避免的,在实际使用中,有时还是需要使用一些其它的外部资源,为了解决这个问题。w3c就制定了一套标准,使得浏览器和服务器之间在跨域可以相互沟通。

3、跨域协商机制

    每一次Ajax请求都会发送HTTP请求,因为,协商机制也就体现在了HTTP报文中,HTTP头部定义了很多可以用来浏览器和服务器协商的字段,通过这些字段,能让服务器对浏览器的请求作出成功或者失败的响应。下面来了解一些浏览器和服务器端经常使用的一些http首部字段:

    浏览器端http报文中一些常见的头部如下:

  • Accept 浏览器能够处理的内容类型,比如Json
  • Accept-Charset 浏览器能够处理的字符类型
  • Accept-Encoding: 能够处理的压缩编码类型,如gzip
  • Accept-Language: 浏览器当前设置的语言
  • Connection: 连接类型,比如keep-alive 长连接
  • Content-type: 内容的类型
  • Host: 目标主机名
  • Origin: 源
  • Referer: 发出请求页面的url

    举个浏览器请求头部的例子

浏览器请求

    服务器端http报文中一些常见的响应头部如下:

  • Access-Control-Allow-Origin: 允许哪个域对资源进行访问,允许全部用*
  • Connection连接的类型
  • Content-Encoding: 编码格式,压缩数据有利于减少阐述时间,浏览器接受后解码
  • Server: 服务器类型
  • Vary:主要用户缓存服务器对于内容压缩的使用,服务器使用浏览器能接受的压缩方式

    举个服务端响应的例子

服务端响应请求

    从上面可以看出,浏览器和服务器对于跨域的协商主要是在Access-Control-Allow-Origin这个值的设置,服务端只有设置了这个字段,才能对资源进行跨域访问。

4、设置Access-Control-Allow-Origin

    在对跨域有了一定的了解之后,对自己项目进行http报文头部分析,发现请求和相应并不在同一个域,而服务端也并未对跨域的相关协商字段进行设置,导致了程序的报错。
    结合下面的图可以看出,不在同一个域
错误的原因
弄清楚了原因,解决方法也很容易就想到了,只要要设置服务端跨域响应的字段,在python中使用装饰器的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
from functools import wraps
from flask import make_response

def allow_cross_domain(fun):
@wraps(fun)
def wrapper_fun(*args, **kwargs):
req = make_response(fun(*args, **kwargs))
req['Access-Control-Allow-Origin'] = '*' # '*'代码允许任意请求
req['Access-Control-Allow-Methods'] = 'PUT,GET,POST,DELETE'
req['Access-Control-Allow-Headers'] = allow_headers
return req
return wrapper_fun

    在路由下添加装饰器即可,如果觉得麻烦,在Flask中也可以使用一个第三方插件,fLask-cors。当然,这里只介绍了一种方式,其实还有很多方式可以解决跨域的问题,比如在Nginx代理中设置Access-Control-Allow-Origin字段,或者使用JSONP,WebSocket都可以。

5、参考

  • JavaScript高级程序设计(第三版)

使用Celery和Redis构建任务队列

发表于 2016-11-05   |   分类于 web   |  

1、flask的阻塞

flask是一个阻塞式的框架,在flask自带的服务器中,一次只能处理一个请求。所以、使用flask开发的网站后台处理网络请求也是这样的,类似于工厂的流水线生产、一个工人只有完成自己手上当前的工作,才能接着完成流水线上过来的另一个工作。

后台开发中,不可避免的会存在一些非常耗时的请求,比如一些IO操作,或者耗时的计算任务。这样导致上一个请求处理完成之前,服务器无法处理其它请求,这样就造成了阻塞。虽然增加线程的数量能在一定程度上缓解这种糟糕的情况,但当并发量非常大时,多线程就显得无能为力。所以,要增加网络的响应速度,处理好耗时的操作是关键。

    在下面的图中,显示了一台小型Linux服务器的负载情况,可以看出,在设置1GB的空间作为交换空间的时候,性能任然捉襟见肘,

1G swap

    在内存很小的情况下,为了避免Linux服务器宕机,设置更大的交换分区可以稍微解决燃眉之急,在设置2G的交换空间之后,情况得到了缓解
:
2G swap

    当然,最好的情况是直接升级服务器的硬件,因为将一部分硬盘设置为交换空间,也会影响磁盘的读写性能,除了硬件升级,优化程序也能带来很大的效率提升。

2、了解redis

    我们已经知道诸如Mysql,sqlserver,Mongodb这类的数据库,这些数据库都有一个共同点,都是将数据存储于硬盘。数据存储在硬盘中的优点是硬盘空间大,但是读写性能的低下也困扰着开发者,特别是对于一些请求量非常大但是变更很少的数据,读取的成本比较高,如果能进行缓存,可以提升很大的IO效率。

    有现实的需求,自然就有相应的工具实现。redis便是一个基于内存也可以持久化的数据库,将数据存储于内存中(当数据大小没有超过”swappability=age*log(size_in_memory)这个值时,超过了便写入硬盘),大大提高热点数据的读取效率。同时,它不仅支持key/value类型的数据,同时还提供了list,hash,set等数据结构的存储。redis的相关使用以后将会用单独的文章来写,这里就不详细描述了

3、Celery

    Celery是使用python编写的一个分布式系统,可以被很多语言编写的程序调用,进行异步任务队列的分发。异步,简而言之就是程序将当前要做的事情先放在后台,稍后再进行处理,缓解当前的压力。

    Celery中有一个重要的概念叫broker,我们知道,Celery中的任务是异步地移交给后台处理,那么怎么能确保系统能记住这个任务不会忘记呢,答案就是这个broker,broker便是存储任务的地方,一般使用RabbitMQ或者Redis。同时,还有一个概念是backend,Celery的异步任务是不能立即返回结果给请求的,如果需要返回结果,便可以先将结果暂存,下次请求就可以直接返回了。

    下面的图展示了任务在redis中的存储情况:
celery-meta-data

    在知道Celery的工作流之后,用个简单的列子来加深一下理解:

1
2
3
4
5
6
7
8
9
10
# -*- coding: utf-8 -*-
from celery import Celery

celery = Celery(__name__, broker='redis://127.0.0.1:6379/0', backend='redis://127.0.0.1:6379/0') # 创建celery对象,任务和结果都存储在redis中

@celery.task
def need_long_time():
pass # 耗时的操作

need_long_time.delay() # 调用任务

在控制台中调用后,可以看到结果是一个AsyncResult:

1
2
3
>>> from task import need_long_time
>>> need_long_time.delay()
<AsyncResult: 4dsade14e-kdjs-45hj-90as-34hjec7kj652>

4、Flask使用异步任务队列

    编写简单的Celery任务小程序很简单,但是将其集成在大型的后台项目,却会遇到一些问题。下面以集成到Flask项目中为列,介绍一下流程。

    由于我的项目使用了抽象工厂的模式,Celery如何在Flask程序中初始化并且如何进入程序上下文,便成为了一个问题,而且Celery只能处于程序上下文之中,在第一次构建之后,使用下面的命令启动单独启动Celery:

1
bash: celery worker -l INFO -A app.main.create_celery.celery # 依据情况替换文件名

报了下面的错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  Traceback (most recent call last):
File "/usr/lib/python2.7/site-packages/celery/task/trace.py", line 228, in trace_task
R = retval = fun(*args, **kwargs)
File "/usr/lib/python2.7/site-packages/celery/task/trace.py", line 415, in __protected_call__
return self.run(*args, **kwargs)
File "/home/ryan/www/CG-Website/src/util/mail.py", line 28, in send_forgot_email
msg = Message("Recover your Crusade Gaming Account")
File "/usr/lib/python2.7/site-packages/flask_mail.py", line 178, in __init__
sender = current_app.config.get("DEFAULT_MAIL_SENDER")
File "/usr/lib/python2.7/site-packages/werkzeug/local.py", line 336, in __getattr__
return getattr(self._get_current_object(), name)
File "/usr/lib/python2.7/site-packages/werkzeug/local.py", line 295, in _get_current_object
return self.__local()
File "/usr/lib/python2.7/site-packages/flask/globals.py", line 26, in _find_app
raise RuntimeError('working outside of application context')
RuntimeError: working outside of application context

    这个错误一看便吻合了我们刚才说的Celery要运行于项目的程序上下文中,经过查阅,Flask给我们提供了相关的操作,因为python的一个进程可以运行多个应用,此时的celery作为一个应用,需要将它绑定到进程中,:

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-
"""
celery_worker
~~~~~~~~~~~~

为celery创建app_context
app: 项目名

"""
from app import create_app, celery

app = create_app()
app.app_context().push()

    做完这一步,根据项目的文件存放位置不同,除了注意这个working outside of application context的坑,还要注意python循环导包的问题,因为有celery的项目文件构成一般较复杂,稍不注意,便容易诱发这个问题。

    上面对工具链和相关问题进行了分析,下面看看运行成功的情况。

    启动Celery程序之后,控制台下会打印启动信息(出错的话错误信息也会在这里打印):
celery-start

    Celery接收任务:
celery-recive-task

    Celery接收多个任务,并完成一些任务,任务完成后会在控制台打印信息,当然在服务器部署的话会打印到log文件:
celery-revice-mult-task

5、参考

  • Celery官方文档
  • Flask中使用Celery上下文的问题

使用阿里云ECS踩坑总结

发表于 2016-04-15   |   分类于 web   |  

ECS系统 ubuntu 14.04 LTS
关于系统,阿里云提供的系统可选项非常多,根据自己的需求安装即可。

1、flask部署环境相关问题

最近在学习flask,部署flask开发的网站,因此在ECS上配置flask web环境就是最重要的了。主要包含了python的一些包,mysql软件等等。下面总结了一些经验。

(1)、安装mysql-server

1
sudo apt-get install mysql-server

安装过程如果出现报错,比如某个url 404错误,可尝试更新系统或者更新软件源

1
2
sudo apt-get update
sudo aot-get upgrade

更改软件源方法此处不赘述,阿里云官方文档有介绍。

在mysql-server安装过程中,会提示用户输入root账户的密码,可按相关提示执行
然后,查看mysql服务的状态,终端下输入:

1
ps -ef | grep mysql

(2)、启动mysql-server

1
service mysql start

(3)、查看mysql数据表

进入mysql-server的安装目录,安装目录在安装的时候系统有提示。
终端下输入

1
mysql -u用户名 -p密码

这样,就进入了mysql的命令行界面,在这里可以对相关数据库进行操作。
输入

1
show databases;

可以看见已经有的数据表

(4)、设置数据库字符编码

要让数据库中显示保存中文不乱码,就要设置mysql的字符编码,打开编辑mysql配置文件my.cnf

1
cd /etc/mysql/my.cnf

(5)、设置远程连接数据库

在mysql命令行下执行下面的命令

1
2
3
4
use mysql;
select Host,User from user;
update user set Host='%' where User='root' limit 1;
flush privileges;

测试是否能连接远程的mysql服务器
在本地电脑的终端中,先安装mysql-client客户端,然后对ecs的数据库发起连接请求,测试能否连接成功

1
mysql -uroot -ppassword -h addr -P port -D database

成功的话自己电脑终端下就会显示mysql命令行界面

(6)、查看mysql使用的端口号

1
show variabels like 'port'

(7)、当忘记了mysql的密码时

在mysql配置文件my.cnf中添加这一行,就跳过密码验证(先关闭mysql服务,再设置)

1
skip-grant-tables

然后重新设置密码

1
update mysql.user set password=password('123') where user='root'

(8)、部署flask生产环境

部署flask的生产环境建议使用virtualenv的虚拟环境,这样不会将各个项目不同的依赖包都导入,直接影响到全局的环境。

1
sudo apt-get install python-pip
1
2
sudo pip install virtualenv
virtualenv venv

就创建了一个名为venv的虚拟环境,要激活虚拟环境,在venv同级目录下执行

1
source venv/bin/activate

如果要退出虚拟环境,执行

1
deactivate

(9)、安装uwsgi

安装uwsgi的时候报错可能会报setuptools的错误,首先要安装编译uwsgi的依赖包
在终端中输入命令

1
2
3
4
pip install python-dev
sudo apt-get install -U setuptools
sudo apt-get install update
sudo apt-get install uwsgi

(10)、解决外网无法访问mysql的问题

检查是否只监听本地的地址,
输入命令

1
netstat -nal |grep 3306

如果是,则需要修改mysql的配置文件,在mysqld下面添加监听0.0.0.0所有的端口

也可能是被防火墙拦截了自己的连接服务,因此需要自己手动设置防火墙
输入命令

1
ptables -A INPUT -p tcp --dport 3306 -j ACCEPT

2、php环境部署相关问题

(1)、安装lamp

1
sudo tasksel install lamp-server

此时,系统可能会给出一个warning,提示要安装语言环境依赖问题,
按照相关的要求解决

1
2
sudo apt-get install language-pack-zh-hans
sudo service apache2 restart

安装phpmyaddmin之后,会发现远程连接phpmyadmin时连接不上,要先用将www和phpmyadmin建立关联:

1
sudo ln -s /usr/share/phpmyadmin /var/www/html/pma

然后登陆ip/pma就可以了
修改phpmyadmin最大文件上传限制大小,默认情况,phpmyadmin将文件上传大小限制在了2M,可以通过更改配置文件修改其大小,
设置php.ini文件,将2M改自己需要的大小

1
2
upload_max_filesize: M
post_max_size: M

(2)、ssh在安装apache后突然连接不上

1
sudo service ssh restart

(3)、ECS重做系统之后发现本地ssh连接不上服务器

发现远程连接不上ssh,因为本地openssh保存公钥在known_hosts文件下 当系统变更后,造成密钥不匹配 解决方法 把该ip地址对应的密钥信息删除就可以了,命令

1
sudo nano /Users/a/.ssh/known_hosts

然后,找到该地址对应的信息,删除即可。

(4)、ubuntu中查找文件夹的名字

1
find / name "fireName"

(5)、ubuntu中的发行版没有httpd.conf配置信息 而是用apache.conf替代了

此外 设置网站的发布目录夹在site-available中也要修改对应的目录

(6)、解决部署网站网站提示缓存神马权限不够的问题

1
sudo chmod 777  /www   www目录替换为自己项目的目录

(8)、解决网站部署后无法访问phpmyaddmin

在apache2.conf中添加phpmyadmin的关联代码,部署网站之后就能通过域名访问pythonmyadmin了

1
Include /etc/phpmyadmin/apache.conf

阿里云ECS部署LAMP环境

发表于 2016-04-09   |   分类于 web   |  

前段时间在阿里云买了台ECS服务器,学生优惠价只要9块9,价格挺良心(后来一查,腾讯云学生优惠只要1元!!!),不过毕竟是国内的主机,限制很多,和域名连用也要备案。因为一个学长开发的一个OnlineJudge评测网站,于是就帮学长把网站部署到了阿里云主机上,方便测试。
网上google之,php的部署环境也有很多种,典型的LAMP(linux+apache+mysq+php),效率高的LNMP(linux+nginx+mysql+php),还有高性能架LNAMP(linux+nginx+apache+mysql+php),个人感觉,没有最好的架构,只有适合自己的架构,于是我选择了LAMP作为部署环境。

1、安装LAMP

一开始天真的想要用apt-get来安装,毕竟它很多的时候是万能的,一行命令就可以搞定安装过程,执行后报各种错误,很多的依赖包找不到。后来放弃了,用tasksel来安装,在终端下执行的命令为:

1
sudo tasksel install lamp-server

安装的过程如果报了perl error语言包得错误,就先安装依赖包,命令如下:

1
sudo apt-get install language-pack-zh-hans

安装过程,会询问你安装mysql数据库的密码,自己设置一下就好。
启动apache2服务后,在浏览器下访问公网ip,可以看见apache的默认html界面,就说明安装成功了。

2、安装phpmyadmin

直接在ECS终端下修改数据库是一个非常耗费时间与精力的过程,因此,我们可以用一些数据库可视化管理软件来管理数据库,比如phpmyadmin,安装命令为:

1
sudo apt-get install phpmyadmin

安装过程会让你选择web服务器,我们选择apache2就可以,同时也需要设置root用户的登录密码。

安装成功后,重启一下apache服务,这样会重新加载配置文件

1
sudo service apache2 restart

然后在自己电脑浏览器下,通过访问公网ip/phpmyadmin,不出意外的话,就可以看见phpmyadmin的界面。当然,如果你有域名,解析后通过域名也可以访问网页了。

3、发布网站

apache发布网站的目录文件夹在 /var/www下,我们需要将网页的文件夹通过ftp工具上传到www目录下,然后修改apache的配置,就可以访问我们的网页了如图。

这里要说的一个坑是,网上很多教程说修改httpd.conf配置,我就一直找啊找 ,发现根本没有这个文件夹,后来才知道,linux的apache发行版的配置都在apache.conf这个文件夹里面,

但是只设置这里并没有用,会发现访问的网页不是我们的主页,而是apache的默认主页,其实是和网页配置目录有关的信息,还存在sites-available文件夹下,我们打开它,修改000-default.conf和default-ssl.conf中的目录。
如图,修改成正确的路径。

4、其它一些坑

  • 我们把网页文件上传到ECS之后,要分配给系统足够的权限(比如读写权限~),不然网页会出现未知的错误
1
sudo chmod 777  /www   www目录替换为自己项目的目录
  • 网站部署后会无法访问phpmyaddmin,因此我们要在apache2.conf中添加phpmyadmin的关联代码
1
Include /etc/phpmyadmin/apache.conf
  • 每次配置修改后,重启一下apache服务器在linux上部署的坑远不止这一点 ,其它的遇到之后再更新。
12
Ddragon

Ddragon

想到什么就写什么

12 日志
3 分类
11 标签
GitHub Weibo 知乎

友情链接

kdwycz's blog MummyDing KevinWu
© 2017 - 2018 Ddragon
由 Hexo 强力驱动
主题 - NexT.Pisces