Docker教程(五)---Azure虚拟安装Docker

老大把Azure的权限给我了,哈哈,可惜我已经准备离职了,但是工作还是要继续的.

一切进行的非常顺利,登录Azure云平台,创建虚机,然后切换root权限安装docker,退出root用户,准备docker pull centos…
无法运行,卧槽!

运行docker命令弹出如下提示:

Cannot connect to the Docker daemon. Is the docker daemon running on this host?

docker出于安全的考虑才会这样做(http://dockone.io/article/589).

解决方案

  1. 切换root权限

    visudo -f /etc/sudoers
  2. /etc/sudoers 中添加如下内容

    azureuser  ALL=(ALL)       NOPASSWD: /usr/bin/docker
  3. 退出root用户

  4. 执行 vim ~/.bashrc 添加

    alias docker='sudo /usr/bin/docker'
  5. 保存后执行

    source ~/.bashrc

此时就可以自由的使用docker命令了.

Docker教程(四)---Dockerfile命令详解

深入docker镜像

拉取镜像文件到本地

docker pull centos

查看本地镜像:

docker images 

查找镜像

docker search [镜像名称]

构建镜像

1. 使用docker commit 命令      用户名:lujiahao
    docker commit 容器名称/ID lujiahao/centos-tomcat
    docker commit -m="describe" --author="lujiahao" 容器名称/ID  lujiahao/centos-tomcat:test
    docker inspect lujiahao/centos-tomcat:test
2. 使用docker build命令和Dockerfile文件(推荐)
    docker build  -t=“用户名/镜像名称" .
    docker build  -t="crxy/centos" .
    点表示会在当前目录中找Dockerfile文件
    建议创建镜像的时候单独一个目录,然后在写Dockerfile,这个目录叫构建目录.如果目录下面有其他文件,会把目录下所有文件发送给docker的守护进程

查看构建的步骤和层级

docker history 用户ID/镜像名

执行流程

  • dockerfile中的指令会按照从上到下执行
  • docker首先从基础镜像运行一个容器
  • 执行一条指令,对容器进行修改
  • 执行类似docker commit的操作,提交一个新的镜像层
  • docker再基于刚提交的镜像运行一个新容器
  • 然后执行dockerfile中的下一条指令,直到所有指令都执行完毕

Dockerfile指令详解

  • 所有指令都必须为大写字母
  • 以#开头的都认为是注释

FROM

第一条指令必须是`FROM`(指定使用的基础镜像)

MAINTAINER

指定镜像的作者信息

RUN

指定镜像被构建时要运行的命令,默认会使用/bin/bash -c 执行RUN 后面指定的命令
也可以使用RUN  ["mkdir","a.txt"]

EXPOSE

告诉docker该容器需要对外开放的端口

CMD

指定一个容器运行时要执行的命令 
docker run -i -t lujiahao/cenos-tomcat /bin/bash
等于在Dockerfile中添加 CMD ["/bin/ps"]
注意:使用 docker run 命令可以覆盖CMD指令

ENTRYPOINT

与CMD类似,但是ENTRYPOINT指定的命令不会被docker run指定的命令覆盖,
它会把docker run命令行中指定的参数传递给ENTRYPOINT指令中指定的命令当作参数。
ENTRYPOINT ["/bin/ps"]
docker run -t -i lujiahao/centos  -l
ENTRYPOINT 还可以和CMD组合一块来用,可以实现,当用户不指定参数的时候使用默认的参数执行,如果用户指定的话就使用用户提供的参数.
ENTRYPOINT ["/bin/ps"]
CMD ["-h"]
注意:如果确实要覆盖默认的参数的话,可以在docker run中指定--entrypoint进行覆盖

WORKDIR

WORKDIR /etc
RUN touch a.txt
WORKDIR /usr/local
RUN touch b.txt
注意:可以在docker run命令中使用-w覆盖工作目录,但是不会影响之前在工作目录中执行的命令

ENV

设置环境变量,在这设置的环境变量可以在后面的指令中使用,使用$引用
并且这个环境变量也会被持久保存到从我们的镜像创建的任何容器中。可以使用env命令查看
也可以使用docker run 命令行的-e参数来传递环境变量,这些变量只在运行时有效 -e JAVA_HOME=/usr/local

USER(了解)

可以指定以什么用户去运行这个容器
USER ftp(用户信息:查看/etc/group)
还可以使用其他组合方式
USER 用户名/UID:组/GID(用户名/UID和组/GID可以单独指定或者组合)
不指定的话默认就是root用户

ADD

ADD指令用来将构建目录下的指定文件复制到镜像中(注意只能是构建目录下面的文件)
ADD a.txt /etc/a.txt
ADD 在添加压缩文件的时候会把压缩文件自动解压。(gzip bzip2 xz),如果目的位置已经存在了和压缩文件同名的文件或者目录,那么这些文件将会被覆盖。

COPY

COPY和ADD类似,但是COPY命令不会对压缩文件进行解压,只负责复制。
如果目的位置不存在,docker会自动创建所有需要的目录,就像使用make -p一样
注意:使用COPY和ADD的命令的时候,复制的源文件必须在当前构建环境之内,也就是和Dockerfile同一目录,否则会找不到。

ONBUILD

这个指令能为镜像添加触发器,当一个镜像被用作其他镜像的基础镜像时,
该镜像中的触发器将会执行。这个触发器所指定的命令会在FROM指令之后执行。
ONBUILD ADD . /usr/local
先在一个镜像中添加这个触发器,再使用这个镜像作为基础镜像创建一个镜像,
会发现在FROM指令之后就开始执行基础镜像中设置的触发器中的指令了。启动一个容器可以查看效果。
注意:触发器只能被继承一次。
有一些命令是不能在ONBUILD中指定的。包括FROM,MAINTAINER,ONBUILD,这样是为了防止在构建过程中产生递归调用的问题。

.dockerignore

在执行build的时候忽略掉不需要的文件。
在Dockerfile同目录下创建.dockerignore,将要忽略的文件填到里面即可.

VOLUME

可以理解为设置(共享目录,磁盘挂载),添加到容器中,并没有真正提交到镜像文件中
这个指令用来向基于镜像创建的容器添加卷,一个卷可以存在于一个或者多个容器内的特定的目录。
卷的特点
    1:卷可以在容器间共享和重用
    2:对卷的修改是立刻生效的
    3:对卷的修改不会对更新镜像产生影响
    4:卷会一直存在直到没有任何容器再使用它。
卷功能可以让我们将一些数据添加到镜像中而不是将将这个内容提交到镜像中。并且允许我们在多个容器间共享这些内容。
    格式:VOLUME  ["/usr/projrct"]
    还可以一次指定多个VOLUME ["/usr/project","/data"]
创建数据卷
    VOLUME  ["/usr/projrct"]
    或者在启动容器的时候创建docker run -it -v /webapp centos /bin/bash
    注意:默认情况下,如果只是声明数据卷而没有映射到宿主机上的具体目录,docker会在/var/lib/docker/vfs/dir/
        目录下分配一个具有唯一名字的目录给该数据卷。通过docker inspect 命令可以查看具体路径
指定宿主主机目录作为数据卷
                        宿主机目录:容器目录
    docker run -it -v /usr/local/webapp:/webapp centos /bin/bash
    注意:如果容器内部已经存在webapp目录,那么目录中的文件将会被覆盖。
默认情况下,数据卷是具有读写权限,rw权限,可以改为只读权限,ro
    docker run -it -v /usr/local/webapp:/webapp:ro centos /bin/bash
使用场景:
    容器里面的tomcat/webapps/目录和本地宿主机的目录对应上,本地修改代码后,
    直接重启容器即可(如果是html连重启都不用了),相当于一个动态部署.

镜像操作

docker的构建缓存

由于每一步的构建过程都会将结果提交为镜像,所以docker的构建镜像过程就显得非常聪明,它会将之前的镜像层看作缓存。再进行重新构建时会从第一条发生了变化的指令开始,前面的都使用缓存

不使用构建缓存的两种方式:

  • 可以使用 --no-cache 参数来指定不使用缓存功能
  • 一般会在dockerfile中使用ENV指令设置一个时间(ENV CREATE_TIME 2015-01-01),写在FROM下面,这样只要一改了这个就不会使用构建缓存了

容器端口映射

默认情况下docker不会自动打开这些端口,必须在运行容器的时候指定-P(大写)参数,这样就可以打开expose指定的所有端口。 会随机映射到宿主机的一个端口上

EXPOSE指令可以同时指定一个或多个需要对外开放的端口

expose 80 8080 6379

对外开放端口的两种方式

-P(大写) 这样的话会将dockerfile文件中EXPOSE 指令指定的所有端口一并公开
-p(小写)可以手工指定需要对外公开的端口

如果没有使用expose指定需要对外开放的端口,还可以在启动容器的时候动态指定,使用-p(小写)参数

-p 80(docker会在宿主机上随机选择一个位于49000~49900内未被使用的端口来映射到容器中的80端口上)
-p 8080:8080(把宿主机上的8080端口映射到容器的8080端口) 前面的表示宿主机
-p 80:80 -p 8080:8080 同时指定多个

使用docker ps 可以查看容器的端口分配情况 或者使用docker port 容器ID/名称查看

推送镜像到Docker Hub(需要先登录:docker login)

docker push 用户ID/镜像名(docker 1.6以下版本这样使用)
docker1.6需要使用下面命令才能推送
docker tag image_id docker.io/login_name/image_name
docker push docker.io/login_name/image_name
推送可能会报错,cdn-registry-1.docker.io: no such host 是因为网络问题、重试几次。

删除镜像

docker rmi 用户ID/镜像名
或者docker rmi `docker images -a -q`
删除所有未打标签的镜像
 docker rmi -f $(docker images --filter 'dangling=true')

Docker教程(三)---容器命令

Docker 容器命令

交互式操作流程

运行一个容器(交互式)

docker run -i -t centos /bin/bash
-i 表示stdin标准输入
-t表示tty终端

第一次执行docker run命令会先从远程仓库中拉取centos的镜像
内部流程如下图

run命令内部流程.jpg

退出容器

exit

重命名容器

docker rename 旧名称 新名称
另一种方法,启动时重命名
docker run --name crxy -t -i centos /bin/bash
合法的容器名称正则[a-zA-Z0-9_.-]

查看容器列表

docker ps [-a]  [-l]  [-n num]

重新启动已经停止的容器

docker start 容器名称/容器ID

附着到一个容器上

docker attach 容器名称/容器ID
    使用`exit`退出容器后,重新由`docker start`命令启动了容器
    此时可以使用这个命令重新进入到容器里面
    可以理解为从外部进入到这个容器内部
    仅仅适用于使用-it启动的容器,像下面的后台启动的是无法使用这个命令的

后台长期运行

创建长期运行的容器

docker run --name lujiahao -d centos /bin/bash -c "while true;do echo hello world;sleep 1;done"

会返回该容器的长id : 55ba56913bb5666ecb352a031503b5191cc8f186558e2f22380c2adadecc7c14

我们通过docker ps获得的是这个长id的前十二位

docker run后面追加-d=true或者-d,那么容器将会运行在后台模式.

获取容器日志

docker logs [--tail num] [-t] [-f]  容器名称/容器ID
    旧版本的日志位置/var/lib/docker/containers/ID/ID-json.log
    但是新版本的实验并没有发现这个log存在位置
    -t 是来添加时间戳的
    -f 持续输出

查看容器内的进程

docker top 容器名称/容器ID

操作一个正在运行的容器

在容器内部运行进程(docker1.3之后才支持)   
docker exec -d lujiahao touch /etc/lujiahao.txt
在后台运行的容器里面创建一个文件
docker exec -it lujiahao /bin/bash
连到一个在后台运行的容器,此时执行exit退出,后台容器还在运行

停止容器

docker stop 容器名称/容器ID  
    向容器发送停止指令,容器自己停止,建议使用
docker kill 容器名称/容器ID   
    直接停掉,不建议使用

获取容器详细信息

docker inspect  [--format | -f] 容器名称/容器ID
    例子:docker inspect --format='{{.State.Running}}' lujiahao
    容器存储位置:/var/lib/docker/containers

删除容器

docker rm 容器名称/容器ID

正在运行中的容器是无法删除的,会出现下面的提示

Error response from daemon: You cannot remove a running container 55ba56913bb5666ecb352a031503b5191cc8f186558e2f22380c2adadecc7c14. 
Stop the container before attempting removal or use -f

可以先停止容器,或者使用 -f 参数

批量删除正在运行中的容器

docker rm `docker ps -a -q`
    docker ps -a -q : 这个命令用来获取所有容器的id

容器的导入和导出

导出:docker export 容器ID/名称 > my_container.tar
导入:cat mycontainer.tar | docker import - 镜像名称:标签

镜像的保存和加载(暂时还没讲到)

保存:docker save 镜像ID > my_image.tar 
加载:docker load< my_image.tar

Docker教程(二)---安装Docker

Docker 核心组件

镜像(Image)

镜像是构建docker世界的基石,也是docker生命周期中的构建阶段.

仓库(Registry)

存储用户构建的镜像以及官方的镜像,分为公有和私有.
Docker公司运营的公有仓库叫做 Docker Hub

容器(Container)

容器是基于镜像启动的,容器中可以运行一个或多个进程。
容器是docker生命周期中的启动或执行阶段。

仓库里面存储着镜像,基于镜像可以启动容器

container = image + docker run

Docker工作流

dockerworkflow.jpg

安装Docker

宿主机Centos7 64位,因为 Docker 是基于64位系统构建的.

  1. 验证linux内核版本uname -a,官方建议使用3.8版本以上

  2. 检查Device Mapper(Docker的存储驱动)

    grep device-mapper /proc/devices
    如果不存在则安装yum install -y device-mapper
    加载dm_mod内核模块 modprobe dm_mod
  3. 安装

    yum -y install docker(默认安装最新版)
  4. 安装指定版本 Docker

    查看可安装的版本
        yum makecache fast(相当于更新本地缓存)
        yum list docker --showduplicates
    安装指定版本
        yum install 2:1.12.5-14.el7.centos
  5. 验证

    启动docker
        systemctl start docker(centos7)
    添加开机启动项
        systemctl enable docker(centos7)
    验证docker是否正常
        systemctl status  docker
        docker info

    出现下面图片表示安装成功了
    dockerinfo.jpg

总结

至此,Docker已经顺利安装了,下面进入到基础命令学习阶段吧!


欢迎大家关注 : LF工作室

简书 : https://www.jianshu.com/u/e61935d18b09

掘金 : https://juejin.im/user/59239002570c350069c5f0bb

微信公众号 :

头条号 :

Docker教程(一)---Docker简介

虚拟化技术

传统虚拟化方式是虚拟机虚拟出一套硬件后,在上面运行一个完整的操作系统;而容器技术是直接使用宿主机的资源,是一种轻量级的虚拟技术.

Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。

VMvsDocker.jpg

这幅图比较了 Docker 和传统虚拟化方式的不同之处,可见容器是在操作系统层面上实现虚拟化,直接复用本地主机的操作系统,而传统方式则是在硬件层面实现。

虚拟机和容器的区别

相同点:

  • 均可在不同主机之间进行迁移
  • 权限级别操作相同

不同点:

  • 虚拟机是硬件虚拟化,容器是系统层面虚拟化
  • 虚拟机臃肿性能有限,迁移难度较大;容器轻量占用空间小,迁移方便,性能接近原生系统
  • 一台服务器支持虚拟机数量有限,容器却可以达到上千个

容器和虚拟机各有各的优缺点,容器也并不是虚拟机的替代品,只是二者在适应不同的需求下各有特点

Docker思想及理念

  • docker 封装思想和 Java 的跨平台思想非常相似
  • 建议一个容器只部署一个应用(当然也可以部署多个)

Docker应用场景

  • 大量部署相同相似环境服务器(例如微信公众号环境)
  • 为开发、测试提供一个轻量级的独立沙盒环境,让应用程序在不同的环境中,得到相同的运行结果
  • 应用的自动化测试/持续集成/自动化打包/发布
  • 高性能、超大规模的宿主机部署

欢迎大家关注 : LF工作室

简书 : https://www.jianshu.com/u/e61935d18b09

掘金 : https://juejin.im/user/59239002570c350069c5f0bb

微信公众号 :

头条号 :

19-JavaWeb基础-Filter

1. 介绍

当访问资源时,如果不希望此资源被访问,可以使用过滤器进行拦截。
访问一个资源,需要路径,过滤器通过路径匹配进行资源拦截。
过滤器必须实现接口:javax.servlet.Filter

Filter
    init(FilterConfig):void        初始化
    doFilter(ServletRequest,ServletResponse,FilterChain):void    过滤方法
    destroy():void        销毁

默认情况下过滤器会拦截匹配路径和执行的资源
需要手动放行:chain.doFilter(request,response);

2.使用方法

详见统计访问次数的案例

3.生命周期

3.1 init(FilterConfig)

初始化方法
    执行时机:服务器启动时执行
    执行者:tomcat
FilterConfig:当前过滤器的配置对象
    getFilterName():String    过滤名称,相当于<filter-name>标签
    getServletContext():ServletContext    ServletContext对象引用
    getInitParametes(String):String    获得过滤器初始化参数
    getInitParameterNames():Enumeration    获得过滤器初始化参数的所有名字

    <filter>
        <filter-name>CountFilter</filter-name>
        <filter-class>com.lujiahao.cms.filter.CountFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
    </filter>

3.2 doFilter(ServletRequest,ServletResponse ,FilterChain)

路径匹配一次执行一次。一次请求拦截一次。

3.3 destroy()

执行时机:服务器正常关闭时执行
执行者:tomcat

4.doFilter方法详解

参数1ServletRequest 和参数2 ServletResponse

回顾:Object               List               ArrayList
      ServletRequest        HttpServletRequest        tomcat实现类
结论:可以强转获得Http协议有关对象
    HttpServletRequest  request = (HttpServletRequest) req;
    HttpServletResponse  request = (HttpServletResponse) res;

参数3:FilterChain

FilterChain 过滤器链,当请求资源时,多个过滤器都生效,tomcat将生成一个链,用于存放多个过滤器。
第一个过滤器执行完成,并发行时,将执行第二个过滤器,。。。。。当最后一个过滤器放行时,将执行资源。

aaaaa.png

过滤器链中 过滤器执行顺序 与 映射在web.xml配置顺序一致的。web.xml 先配置先执行

5.<url-pattern>匹配路径

回顾:servlet路径配置

1.完全匹配,必须/开头 ,例如:/a/b/c/oneServlet
2.不完全匹配,以/开头   *结尾,例如:/a/b/*  , /*
3.通配符匹配. *.开头,例如: *.jsp  、 *.do   、*.action 等
4 缺省  /   以上都没有匹配将执行。

过滤器路径编写,与servlet一致的,过滤器过滤的就是指定servlet。

/oneServlet  、 /twoServlet
/*   过滤器 将匹配以上两个servlet

6.dispatcher配置

用于配置过滤器在何处进行拦截。

取值:    REQUEST、 FORWARD、 INCLUDE、ERROR
request : 表示在请求开始时拦截。默认值。
forward:表示在请求转发开始时拦截。及 request.getRequestDistacher(..).forward 被调用时。
include:表示在请求包含开始时拦截。及 request.getRequestDistacher(..).include 被调用时。
error : 表示程序异常,显示友好页面之前拦截。

案例〇 : dispatcher配置友好错误页面

IE会将500的错误以他自己的方式展现出来,这就不符合需求,所以需要手动配置友好错误页面.
如果有多个友好错误界面,不能在每个jsp页面更改状态码,这就需要一个过滤器来统一过滤.

1.创建FriendErrorFilter实现javax.servlet.Filter接口

/**
 * 友好错误界面过滤器
 * Created by lujiahao on 2016/7/27.
 */
public class FriendErrorFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 修改状态码
        response.setStatus(200);
        // 放行
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}

2.配置到tomcat(web.xml)

<!--配置友好错误页面过滤器-->
<error-page>
    <error-code>500</error-code>
    <location>/demo0/frienderror.jsp</location>
</error-page>
<!--友好错误页面过滤器-->
<filter>
    <filter-name>FriendErrorFilter</filter-name>
    <filter-class>com.lujiahao.filter.FriendErrorFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>FriendErrorFilter</filter-name>
    <url-pattern>/frienderror.jsp</url-pattern>
    <dispatcher>ERROR</dispatcher>
</filter-mapping>

3.编写友好错误界面(error.jsp)

<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        我是友好错误界面<br/>
        服务器繁忙,请稍后重试
    </body>
</html>

4.模拟服务器500错误

<%
    int i = 1/0;
%>

案例一 : 统计访问次数

原理:将数据记录到ServletContext 所有用户共享

1.创建CountFilter实现javax.servlet.Filter接口

/**
 * 统计次数的过滤器
 * Created by lujiahao on 2016/7/27.
 */
public class CountFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // 获得访问路径   /pages/main/main.jsp
        String servletPath = request.getServletPath();
        String filePath = servletPath.substring(servletPath.lastIndexOf("/")+1,servletPath.lastIndexOf("."));

        // 获得servletContext获取统计数据
        ServletContext servletContext = request.getSession().getServletContext();
        Integer num = (Integer) servletContext.getAttribute(filePath);
        if (num == null) {
            num = 1;// 第一次访问
        } else {
            num ++;
        }
        // 存放到ServletContext中
        servletContext.setAttribute(filePath,num);

        // 放行
        filterChain.doFilter(request,response);
    }

    @Override
    public void destroy() {

    }
}

2.配置到tomcat(web.xml)

<!--统计次数的过滤器-->
<filter>
    <filter-name>CountFilter</filter-name>
    <filter-class>com.lujiahao.cms.filter.CountFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CountFilter</filter-name>
    <url-pattern>/pages/main/main.jsp</url-pattern>
</filter-mapping>

3.JSP中使用

访问次数:${applicationScope.main}次<br/>

案例三 : 自动登录

原理图:
QQ截图20160727163940.png

1.修改以前的登录逻辑,添加对自动登录的标记

/**
 * 登录操作
 */
private void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 1.获取数据并封装
    Customer customer = LBeanUtils.populate(Customer.class, request.getParameterMap());
    // 2.通知service进行登录
    CustomerServeice customerServeice = new CustomerServiceImpl();
    Customer loginCustomer = customerServeice.login(customer);

    // 3.处理
    if (loginCustomer != null) {
        // -------------自动登录start-------------
        // checkbox选中的话获得的值是on
        String autologin = request.getParameter("autologin");
        if (autologin != null) {
            // 勾选了记住密码,  使用cookie将用户的登录名和密码发送到浏览器,并通知浏览器保存(持久化cookie)
            // 1.创建cookie对象
            String username = URLEncoder.encode(loginCustomer.getName(), "UTF-8");
            String password = URLEncoder.encode(loginCustomer.getPwd(), "UTF-8");
            // 存入需要进行URLEndode编码,不然会报错:http://blog.csdn.net/liuxiao723846/article/details/22155393
            Cookie cookie = new Cookie("autoLoginCookie", username + "&" + password);
            // 2.设置有效时间
            cookie.setMaxAge(60 * 60);// 1小时
            // 3.设置路径
            cookie.setPath("/");
            // 4.发送cookie
            response.addCookie(cookie);
        }
        // -------------自动登录end-------------

        // 成功  -- session记录登录状态,重定向到主页面
        // * session作用域保存数据
        request.getSession().setAttribute("loginCustomer", loginCustomer);
        response.sendRedirect(request.getContextPath() + "/pages/main/main.jsp");
    } else {
        // 不成功  --- request记录当次请求提示,请求转发到login.jsp 显示数据
        request.setAttribute("msg", "用户名和密码不匹配");
        request.setAttribute("customer", customer);// 回显数据
        request.getRequestDispatcher("pages/login/login.jsp").forward(request, response);
    }
}

2.编写自动登录的过滤器

/**
 * 自动登录的过滤器
 * Created by lujiahao on 2016/7/27.
 */
public class AutoLoginFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        Customer loginCustomer = (Customer) request.getSession().getAttribute("loginCustomer");
        // 1.已经登录
        if (loginCustomer != null) {
            filterChain.doFilter(request,response);
            return;
        }

        // 2.没有登录,获取cookie信息
        Cookie[] allCookie = request.getCookies();
        String cookieValue = null;
        if (allCookie != null) {
            for (Cookie cookie : allCookie) {
                if ("autoLoginCookie".equals(cookie.getName())) {
                    cookieValue = cookie.getValue();
                    break;// 一旦查询到了就跳出循环,提升性能
                }
            }
        }

        // 3.没有cookie信息
        if (cookieValue == null) {
            filterChain.doFilter(request,response);
            return;
        }

        // 4.查询到cookie信息,获得用户数据
        String username = URLDecoder.decode(cookieValue.split("&")[0],"UTF-8");
        String password = URLDecoder.decode(cookieValue.split("&")[1],"UTF-8");
        Customer customer = new Customer(username,password);

        // 5.查询用户信息
        CustomerServeice customerServeice = new CustomerServiceImpl();
        loginCustomer = customerServeice.login(customer);

        // 6.没有查询到用户信息----例如:密码修改
        if (loginCustomer == null) {
            filterChain.doFilter(request,response);
            // 应该删除cookie
            Cookie cookie = new Cookie("autoLoginCookie","");
            cookie.setMaxAge(0);// 删除
            cookie.setPath("/");// 保存的时候写的路径要和这里一样
            response.addCookie(cookie);
            return;
        }

        // 7.查询到用户信息,执行自动登录
        request.getSession().setAttribute("loginCustomer",loginCustomer);

        // 放行
        filterChain.doFilter(request,response);
    }
    @Override
    public void destroy() {

    }
}

案例四 : GET/POST中文乱码统一处理

1.旧的处理方式:

public class GetPostServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        //处理编码
        username = new String(username.getBytes("ISO8859-1"),"UTF-8");
        password = new String(password.getBytes("ISO8859-1"),"UTF-8");

        System.out.println("get:");
        System.out.println(username);
        System.out.println(password);
        */
        this.doPost(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //post请求乱码
        //request.setCharacterEncoding("UTF-8");

        String username = request.getParameter("username");
        String password = request.getParameter("password");

        System.out.println("post:");
        System.out.println(username);
        System.out.println(password);
    }
}

2.使用过滤器+装饰着模式的处理方式:

/**
 * 处理Get/Post请求乱码的过滤器
 * Created by lujiahao on 2016/7/27.
 */
public class GetPostEncodingFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //编码设置 另一个filter编写   这个只能解决POST乱码的问题
        request.setCharacterEncoding("UTF-8");

        // 这个里面有处理GET乱码
        // 使用装饰者模式增强的request
        MyRequest myRequest = new MyRequest(request);
        // 放行
        filterChain.doFilter(myRequest,response);
    }

    @Override
    public void destroy() {

    }
}

/**
 * 使用装饰者设计模式
 * HttpServletRequestWrapper是系统为我们准备好的装饰者的父类,已经完成了相应的工作
 * Created by lujiahao on 2016/7/27.
 */
public class MyRequest extends HttpServletRequestWrapper {
    private boolean isEncoding = false;// 默认是没有编码的
    private HttpServletRequest request;
    public MyRequest(HttpServletRequest request) {
        super(request);// 将tomcat的request传入父类,提供给不需要增强的方法使用
        this.request = request;
    }

    // 需要增强的方法直接覆写父类方法就好了

    // 通过名称获得第一个值
    @Override
    public String getParameter(String name) {
        String[] parameterValues = this.getParameterValues(name);
        if (parameterValues == null) {
            return null;
        }
        return parameterValues[0];
    }

    // 通过名称获得所有的值
    @Override
    public String[] getParameterValues(String name) {
        return this.getParameterMap().get(name);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        // 1.获得tomcat原始数据
        Map<String, String[]> map = request.getParameterMap();
        // 2.处理get请求的乱码
        if ("GET".equals(request.getMethod()) && !isEncoding) {
            // 3.遍历map
            for(Map.Entry<String,String[]> entry : map.entrySet()){
                // 4.获得所有value数据
                String[] value = entry.getValue();
                // 处理所有乱码
                for (int i = 0; i < value.length; i++) {
                    try {
                        value[i] = new String(value[i].getBytes("ISO-8859-1"),"UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            isEncoding = true;// 已经解决完乱码就不要再次解决了
        }
        return map;
    }
}

案例五 : 页面静态化 & 全栈压缩

页面静态化原理图:
QQ截图20160728133730.png
1.装饰者模式增强HttpServletResponse

public class MyResponse extends HttpServletResponseWrapper{

    private HttpServletResponse response;

    //提供自定义缓存
    private ByteArrayOutputStream baos = new ByteArrayOutputStream();

    private PrintWriter pw = null;
    public MyResponse(HttpServletResponse response) {
        super(response);
        this.response = response;
    }

    @Override
    public PrintWriter getWriter() throws IOException {
        if(pw == null){
            pw = new PrintWriter(new OutputStreamWriter(baos , "UTF-8" ));
            System.out.println("执行" + pw);
        }
        return pw;
    }

    /**
     * 获得自定义缓存内容
     * @return
     */
    public byte[] getData(){
        return baos.toByteArray();
    }

}

2.过滤器写法

public class PageStaticFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;

        //1 获得页面位置
        //D:\java\tomcat\apache-tomcat-7.0.53\webapps\day19_demo\demo04\1.html
        ServletContext sc = request.getSession().getServletContext();
        String id= request.getParameter("id");
        String path = "/demo04/"+id+".html";// 静态页面存放的位置
        String htmlPath = sc.getRealPath(path);

        //2 文件是否存在
        File htmlFile = new File(htmlPath);
        if(htmlFile.exists()){
            //存在就显示
            request.getRequestDispatcher(path).forward(request, response);
            return;
        }

        // 自定义response 提供缓存,用于存放 发送到浏览器所有内容
        MyResponse myResponse = new MyResponse(response);
        //放行
        chain.doFilter(request, myResponse);

        //内容已经写入到自定义缓存
        byte[] arrayByte = myResponse.getData();

        /***写入到文件 start**/
        //将指定的内容内容到文件
        FileOutputStream out = new FileOutputStream(htmlFile);
        out.write(arrayByte);
        out.close();

        //手动写入到浏览器
        response.getOutputStream().write(arrayByte);
        /***写入到文件 end**/

        /**全站压缩 , 将需要发送的数据,压缩之后再发给浏览器,节省流量
            全站压缩和页面静态化是冲突的,需要一个一个来测试**/
        /*
        // 设置发送的数据位压缩数据
        response.setHeader("content-encoding", "gzip");
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); //存放压缩后的结果
        GZIPOutputStream gzipOut = new GZIPOutputStream(baos); //压缩的位置
        gzipOut.write(arrayByte); //需要压缩的原始数据
        gzipOut.close();

        response.getOutputStream().write(baos.toByteArray());
        */
    }

    @Override
    public void destroy() {
    }
}

扩展:装饰者设计模式

乱码解决的十几分钟的时候讲了这个东西

jar包和war包的区别

java项目 压缩 :jar包
web项目 压缩:war包 。 当war添加到tomcat/webapps下将自动解压
jar包和war包压缩格式都是 zip

升级订制版HttpFilter

这个非常好,用到的思想和Servlet中的service()方法非常类似,好好总结一下
http://www.cnblogs.com/jianjianyang/p/5001471.html

11-JavaWeb基础-EL-JSTL

EL表达式

EL介绍

EL(Expression Language) 目的:为了使JSP写起来更加简单。表达式语言的灵感来自于 ECMAScript 和 XPath 表达式语言,它提供了在 JSP 中简化表达式的方法,让Jsp的代码更加简化。

阅读更多

10-JavaWeb基础-JSP笔记

什么是JSP

  • tomcat安装目录下的work目录就是tomcat处理jsp的工作目录
  • JSP是以Java语言为基础的动态网页开发技术(Servlet也是动态网页开发技术)
  • 二者特点
    • servlet特点:在java代码中嵌入html代码
    • jsp特点:在html代码中嵌入java代码
阅读更多

09.JavaWeb基础-cookie-session

1.会话技术

会话可简单理解为:用户开一个浏览器,点击多个超链接(多次请求),访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。

会话技术:

cookie:浏览器端会话技术
session:服务器端会话技术
目的:在一次会话中(多次请求)共享数据。

aaa.png

3 cookie技术
 cookie技术不局限java,其他语言也支持。例如:php、javascript等
 JavaEE 规范提供工具类对Cookie进行操作。javax.servlet.http.Cookie类
 cookie 是什么
1. servlet创建cookie,保存少量数据,发送浏览器。
2. 浏览器获得服务器发送的cookie数据,将自动的保存到浏览器端。
3. 下次访问时,浏览器将自动携带cookie数据发送给服务器。
 cookie操作
1.创建cookie:new Cookie(name,value)
2.发送cookie到浏览器:HttpServletResponse.addCookie(Cookie)
3.servlet接收cookie:HttpServletRequest.getCookies() 浏览器发送的所有cookie
 cookie特点
每一个cookie文件大小:4kb , 如果超过4kb浏览器不识别
一个web站点(web项目):发送20个
一个浏览器保存总大小:300个
cookie 不安全,可能泄露用户信息。浏览器支持禁用cookie操作。
默认情况生命周期:与浏览器会话一样,当浏览器关闭时cookie销毁的。—临时cookie

 cookie api
getName() 获得名称
getValue() 获得值

setValue(java.lang.String newValue)  设置内容
setMaxAge(int expiry) 设置有效时间【】
setPath(java.lang.String uri)  设置路径【】
setDomain(java.lang.String pattern) 设置域名 , 一般无效,有浏览器自动设置,setDomain(".itheima.com")
    www.itheima.com / bbs.itheima.com 都可以访问
    a.b.itheima.com无法访问
isHttpOnly()  是否只是http协议使用。只能servlet的通过getCookies()获得,javascript不能获得。

setComment(java.lang.String purpose) (了解)
setSecure(boolean flag) (了解)
setVersion(int v) (了解)

3.1 路径
 cookie默认路径:当前访问的servlet 父路径
例如:http://localhost:8080/day09/a/b/c/SendCookieServlet
默认路径: /day09/a/b/c/
 通过 setPath 修改cookie访问路径,一般使用:setPath(“/“) –web站点的根(没有项目名)
设置路径与servlet访问无关。servlet 去获得cookie有关。
 如果编写一个servlet 获得cookie GetCookiesServlet –> getCookies()
http://localhost:8080/day09/a/b/c/SendCookieServlet1 –》/day09/a/b/c/
http://localhost:8080/day09/a/b/SendCookieServlet2 –》/day09/a/b/
http://localhost:8080/day09/a/SendCookieServlet3 –》/day09/a/
http://localhost:8080/day09/d/SendCookieServlet4 –》/day09/d

如果此获得cookie 访问路径
    http://localhost:8080/day09/a/b/c/ ,使用项目之后内容"/day09/a/b/c/".startWith(....)
            访问的servlet路径,必须与cookie本地设置路径 判断。
    通过getCookies()获得 cookie1/cookie2/cookie3
通常使用setPath("/") 其他servlet "/day09".startWith("/") 肯定都是/开头。所以可以访问
    通途:保证在tomcat下所有的web项目可以共享相同的cookie    
    例如:tieba , wenku , beike 多个项目共享数据。例如用户名。

 当前项目访问:setPath(“/day09/“)
3.2 有效时间
 默认情况:cookie临时,当浏览器关闭销毁的。
 setMaxAge 可以修改cookie被浏览器保存的时间。浏览器将cookie信息将保存cookie文件,浏览器关闭后文件仍然存在,直到设置时间过期将被浏览器自动删除。单位:秒 – 持久化cookie(保存文件)
 删除cookie:保证域名、路径和cookie名称一致情况下,设置 setMaxAge(0) 将删除。

 cookie唯一标识:域名、路径、名称
localhost / demo03_cookie_key
baidu.com / mk
localhost /day09/a/ cookie_key
3.3 发送中文数据
 cookie 使用http协议请求头和响应头,http协议不支持中文,cookie本身不支持中文的。
 如果cookie value设置中文,服务器将抛异常。
 如果cookie需要写入中文,必须手动编码(发送),将编码后结果发送浏览器,之后浏览器返回给服务器仍然编码后的结果,还需要手动解码(获取)。
 JDK提供工具,进行编码
URLEncoder:编码
URLDecoder:解码
//编码
String str = URLEncoder.encode(“屌中屌”, “UTF-8”);
System.out.println(str); //%E5%B1%8C%E4%B8%AD%E5%B1%8C
//解码
String value = URLDecoder.decode(str, “UTF-8”);
System.out.println(value);

3.4 cookie案例1:记住用户名

  1. 表单可以提交(文本框、复选框) —下次浏览时,如果曾经记录应该将记录用户名显示到文本框中。
    表单必须是servlet输出,不能是html页面,否则文本框不能显示记录数据。(除非jsp)
  2. 点击提交,编写servlet处理(是否勾选)
  3. 如果勾选,记录用户名(记录数据下次还可以访问,所以cookie,持久cookie setMaxAge(….))
  4. 如果没有勾选,删除cookie,注意:唯一标识(域名、路径、名称),cookie必须再次发送到浏览器。

扩展:将UrlEncoder 和 UrlDecoder 结合使用(解决中文用户名)

3.5 cookie案例2:历史记录

  1. 点击查询所有商品
  2. 通过id查询商品详情 – 应该记录浏览记录
  3. 查询所有的浏览记录,显示记录数据

4 session
 经典应用:用户登录、JD购物车等
 一次会话中,服务器用于共享数据技术。
 默认情况:session需要基于cookie使用。及没有cookie session无效。
当第一次调用 request.getSession() ,tomcat将创建一个session对象,并将session对象id值,以cookie方式发送给浏览器,之后浏览器再次请求时,将session id 发送服务器,服务器通过request.getSession() 就可以获取之前已经创建好的session对象。
 JavaEE规范提供接口:javax.servlet.http.HttpSession 用于描述session对象。
 获得session
request.getSession() 获得session,如果没有将创建一个新的。等效request.getSession(true)
request.getSession(boolean) 获得session,true:没有将创建,false:没有将返回null
 session属性操作:xxxAttribute
session 也是 servlet 域对象。(3个servlet域对象:ServletRequest、HttpSession、ServletContext)
 session生命周期
创建:第一次调用 getSession()时创建
销毁:
1. 超时,默认30分钟。
web.xml 中配置

30 单位:分钟

2.执行api
invalidate() 将session对象销毁了。
setMaxInactiveInterval(int interval) 设置有效时间,单位秒
3.服务器非正常关闭。(自杀,将JVM马上关闭)
如果正常关闭,session将被持久化(写入到文件中)
D:\java\tomcat\apache-tomcat-7.0.53\work\Catalina\localhost\day09\SESSIONS.ser
当tomcat重新启动的时候这个文件就会被删除

4.1 URL重写
 当浏览器将cookie禁用,基于cookie的session将不能正常工作,每次使用request.getSession() 都将创建一个new session。只需要将session id 传递给服务器session就可以工作的。
 通过URL将session id 传递给服务器:URL重写
手动方式: url;jsessionid=….
api方式:
encodeURL(java.lang.String url) 进行所有URL重写
encodeRedirectURL(java.lang.String url) 进行重定向 URL重写
如果参数url为空字符不同,其他都一样。
如果浏览器禁用cooke,api将自动追加session id ,如果没有禁用,api将不进行任何修改。
注意:如果浏览器禁用cookie,web项目的所有url都需手动重写。否则session将不能正常工作。

5 总结
cookie
浏览器会话技术,用于在浏览器缓存内容,达到数据共享的目的
使用
1.服务器创建对象,添加数据
2.服务器将cookie发送给浏览器
3.浏览器自动携带cookie数据,服务器获得数据。
浏览器携带数据,根据cookie路径设置。
api
setMaxAge 有效时间,0删除,必须保证路径匹配
setPath 设置访问路径
唯一标识:域名、路径、名称
session
服务器端会话技术,用于在服务器共享数据。
session必须使用cookie传递session id,保证多次请求,浏览器可以共享 服务器一个session对象。
如果浏览器禁用cookie,必须进行URL重写。
session生命周期:

08.JavaWeb基础-Response-Request

1.Response

1.1 响应行

setStatus(int status);// 设置状态码

1.2 响应头

setHeader(String name, String value) 设置指定的头,一般常用。
    例如:setHeader("location","http://www.itheima.com");
setDateHeader(String name, long date) 设置时间的
    这里使用long的原因是因为 : new Date().getTime()的返回值是long
setIntHeader(String name, int value) 设置整型
    例如:setIntHeader("expires", 1000) 设置过期时间的(缓存页面时间)。0表示不缓存现代浏览器不好使
阅读更多