第9章WEB09-Servlet篇

今日任务

  • 完成系统的登录的功能
  • 完成登录系统后页面定时跳转
  • 记录系统登录成功后,系统被访问多少次

教学导航

教学目标

了解HTTP协议

掌握Servlet的编写

了解ServletConfig的使用

掌握ServletContext对象的使用

教学方法

案例驱动法

1.1      上次课内容回顾:

XML     :

* XML的概述:

* 什么是XML:可扩展标记语言.

* XML的作用:作为软件的配置文件,传输和存取数据.

* XML的使用:

* XML的基本语法:

* 标签必须有开始和结束.

* 必须有跟标签

* 区分大小写.

* 正确嵌套.

* XML的文档声明:<?xml version=”1.0” encoding=”UTF-8”?>

* XML的注释:<!-- 注释 -->

* XML的元素:

* 命名规范:不能以数字开头,标签包含字母或数字,不能以xml开头,不能出现空格 冒号.

* XML的属性:

* 命名规范:与元素一致.属性必须加引号.

* XML的特殊字符和CDATA区.

* XML的解析:

* XML的解析方式有哪些:

* DOM和SAX:

* DOM一次性将文档加载到内存,形成树形结构.如果文档过大容易导致内存溢出.方便增删改的操作.

* SAX边读边解析的方式,如果文档大,不会导致内存溢出,但是不能进行增删改.

* 常见的解析XML的API:

* JAXP,JDOM,DOM4J...

* 使用DOM4J解析XML:

* XML的约束:(了解)

* XML的约束有哪些区别是什么?

* DTD和Schame:

* DTD的语法是自成一体,Schema使用的XML的语法.

* Schema有比DTD更强大的语义和语法的约束.

* Schema支持名称空间.

第2章     Tomcat

* WEB的相关的内容:

* 软件架构:

C/S   B/S

* 服务器:就是一台电脑,这台电脑上安装了服务器的软件.

* 常见的WEB服务器:WebSphere,WebLogic,Tomcat,JBoss,IIS,apache...

* 使用Tomcat:

* 下载和安装Tomcat:

安装:直接解压即可

* Tomcat的目录的结构:

* tomcat/conf:

* tomcat/webapps:

* tomcat/logs:

* tomcat/bin:

2.1.1    * 发布一个web项目到服务器的方式:(仅仅是个了解以后肯定不用这种方式的)

* 一、复制项目到webapps下.

* 二、修改server.xml,在<host>标签下配置<Context path=”” docBase=””/>(不推荐的)

docBase实际路径    path虚拟路径

* 三、在conf/Catalina/localhost/xxx.xml 配置<Context docBase=””/>(推荐用)

* 使用STS配置Tomcat:

1、指定sts的java环境

找到jdk的安装路径:

 

 

 

 

 

 

 

 

 

2、修改工作空间的编码

3、创建动态的web工程

 

 

 

 

 

 

 

4、集成tomcat

2.2      案例一:使用Servlet完成一个用户登录的案例.

2.2.1    需求:

在网站的首页上,登录的链接,点击登录的链接,可以跳转到登录的页面.在登录的页面中输入用户名和密码点击登录的案例.完成登录的功能.

2.2.2    分析:

2.2.2.1  技术分析:

【HTTP的协议的概述】

  • 协议:

* 什么是协议:规定双方需要遵守的规则.

  • HTTP协议:

* 什么是HTTP协议:用来规定浏览器与服务器之前需要遵守的规则.

  • HTTP协议的作用:规范浏览器和服务器之间的数据传递.
  • HTTP协议的特点:

* 基于请求和响应的模型.

* 必须先有请求后有响应.

* 请求和响应必须成对出现.

* 默认的端口号是80.

  • HTTP协议的版本:

* 1.0   :每次响应后即刻关闭了连接.

* 1.1   :现在使用.不是每次响应后挂断,等待长时间以后没有请求会挂断.

【HTTP协议的演示】

抓包分析:GET方式:

浏览器抓包方式:ctrl+ shift+ i

* 请求部分:

GET /day09/demo1-http/demo2.html?name=aaa&age=23 HTTP/1.1

Accept: text/html, application/xhtml+xml, */*

X-HttpWatch-RID: 59176-10011

Referer: http://localhost:8080/day09/demo1-http/demo1.html

Accept-Language: zh-CN

User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko

Accept-Encoding: gzip, deflate

Host: localhost:8080

DNT: 1

Connection: Keep-Alive

抓包分析:POST方式:

POST /day09/demo1-http/demo2.html HTTP/1.1

Accept: text/html, application/xhtml+xml, */*

X-HttpWatch-RID: 59176-10031

Referer: http://localhost:8080/day09/demo1-http/demo1.html

Accept-Language: zh-CN

User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip, deflate

Host: localhost:8080

Content-Length: 15

DNT: 1

Connection: Keep-Alive

Cache-Control: no-cache

name=bbb&age=38

 

* 响应部分:

HTTP/1.1 200 OK

Server: Apache-Coyote/1.1

Accept-Ranges: bytes

ETag: W/"145-1461807615933"

Last-Modified: Thu, 28 Apr 2016 01:40:15 GMT

Content-Type: text/html

Content-Length: 145

Date: Thu, 28 Apr 2016 01:43:52 GMT

<!DOCTYPE html>

<html>

<head>

<meta charset="UTF-8">

<title>Insert title here</title>

</head>

<body>

<h1>Demo2.html</h1>

</body>

</html>

【HTTP协议的详解】

  • 请求部分

* 请求行

* 提交方式:

* 提交方式有很多,常用的GET和POST:

* GET和POST的区别:分角度回答:1/数据安全2/携带数据量

* GET的提交的参数会显示到地址栏上,而POST不显示.

* GET往往是有大小限制的,而POST没有大小的限制.

* GET没有请求体,而POST有请求体.

* 提交路径:

* 协议版本:现在都是用1.1版本

* 请求头

* 都是键值对的形式显示的.一般一个key对应一个value,也有个别的是一个key对应多个value.

* User-Agent             :代表浏览器的类型. --- 文件下载:下载中文文件:IE使用URLEncodor进行编码,而Firefox使用Base64编码.

* Referer                 :代表的是网页的来源. --- 防盗链.

* If-Modified-Since      :通常与响应中的头Last-Modified一起使用查找本地缓存.

* 请求体

* 就是POST提交方式的提交的参数.

  • 响应部分

* 响应行:

* 协议版本

* 状态码      :

* 200 :成功

* 302 :重定向

* 304 :查找本地缓存

* 404 :资源不存在         404 路径写错了  超链接地址写错了  服务器不存在这种资源

* 500 :服务器内部错误     java代码有问题

* 状态码描述

* 响应头:键值对,一般一个key对应一个value,也有一个key对应多个value.

* Last-Modified          :与请求中的If-Modified-Since一起使用查找本地缓存.

* Content-Disposition    :文件下载的使用使用的一个头信息.

* Location               :重定向的跳转的路径.

* Refresh                :定时刷新/定时跳转.

* 响应体:显示浏览器的页面的内容.

在线html的教程网址:扫盲教程

第3章     【Servlet的概述】

  • 什么是Servlet:servlet是一个接口

* 就是一个运行在WEB服务器上的小的Java程序,用来接收和响应从客户端发送过来的请求,通常使用HTTP协议.

* Servlet就是SUN公司提供的一个动态网页开发技术.

  • Servlet的作用:

* 用来处理从客户端浏览器发送的请求,并且可以对请求作出响应

  • 使用Servlet

* 编写一个类实现Servlet接口.

* 将编写的这个类配置到服务器中.

  • Servlet的入门:

* 编写类:

public class ServletDemo1 implements Servlet{

@Override

/**

* 为用户处理请求和响应的方法.req请求 res响应

*/

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {

res.getWriter().println("Hello Servlet...");

}

...(其他方法)

}

* 配置:

<!-- 配置Servlet -->

在web.xml中配置

配置的具体内容及位置:

<servlet>

<!-- Servlet的名称 -->

<servlet-name>test1</servlet-name>

<!-- Servlet的全路径 -->

<servlet-class>com.itheimweia.a_servlet.ServletDemo1</servlet-class>

</servlet>

<!-- Servlet的映射e -->(利用发射获取里边的属性)

<servlet-mapping>

<!-- Servlet的名称 -->

<servlet-name>test1</servlet-name> 这个名字可以随便取

<!-- Servlet的访问路径 -->

<url-pattern>/ServletDemo1</url-pattern> 应该是固定的。

</servlet-mapping>

* 访问:

http://localhost:8080/day09/ServletDemo1  项目名和类名

【使用ServletRequest接收参数】ServletRequest是个接口

* String getParameter(String name);     ---用于接收一个名称对应一个值的数据.

String getParameter(String name)中写的是表单中写的username值

例:String username = request.getParameter(“username”)  表单中必须要有name这个属性还有属性值。Request在doget()方法中

* String[] getParameterValues(String name);---用于接收一个名称对应多个值的数据.

* Map getParameterMap();                    ---用于接收表单中的所有的数据,Map的key是表单提交的还有参有数名称,Map的value是提交参数的值.

* Enumeration getParameterNames()           ---用于获取表单中提交的所有的参数的名称.

【Servlet的访问流程】

【Servlet的实现的关系】

Servlet         :接口

|

GenericServlet  :通用的Servlet 这是个类 实现servlet接口

|

HttpServlet     :HttpServlet专门用于http的   是genericservlet类的子类

* 编写一个类继承HttpServlet,重写doGet和doPost方法.编写的时候直接就集成httpservlet就行

* 配置

Servlet的实现关系:

只调用doget和dopost而不重写service方法的原因就是service调用了doget和dopost方法

重写了doget和dopost方法就相当于重写了service方法 这就是原因。

3.1.1    代码实现

3.1.1.1  步骤一:创建数据库和表:

create database day09;

use day09;

create table user(

id int primary key auto_increment,

username varchshihar(20),

password varchar(20),

nickname varchar(20)

);

insert into user values (null,'aaa','111','小凤');

insert into user values (null,'bbb','111','小童童');

3.1.1.2  步骤二:创建包和类:

3.1.1.3  步骤三:引入jar包

* mysql的数据库的驱动包

* c3p0连接池的jar包

* dbutils的包

关系:

3.1.1.4  引入login的页面

3.1.1.5  编写Servlet-->Service-->DAO

3.1.2    总结:

3.1.2.1  Servlet的生命周期:(*****)

  • 生命周期:就是一个对象从创建到销毁的过程.
  • Servlet生命周期:Servlet从创建到销毁的过程.

* 何时创建:用户第一次访问Servlet创建Servlet的实例

* 何时销毁:当项目从服务器中移除的时候,或者关闭服务器的时候.

  • 用户第一次访问Servlet的时候,服务器会创建一个Servlet的实例,那么Servlet中init方法就会执行.任何一次请求服务器都会创建一个新的线程访问Servlet中的service的方法.在service方法内部根据请求的方式的不同调用doXXX的方法.(get请求调用doGet,post请求调用doPost).当Servlet中服务器中移除掉,或者关闭服务器,Servlet的实例就会被销毁,那么destroy方法就会执行.

3.1.2.2  Servlet的相关的配置:

【启动时创建Servlet】

Servlet默认是在第一次访问的时候创建的.现在让Servlet在服务器启动的时候创建好.进行对Servlet的配置:

在web.xml中在<servlet></servlet>标签中配置:

* <load-on-startup>2</load-on-startup>  --- 传入正整数,整数越小,被创建的优先级就越高.

【url-pattern的配置】

url-pattern配置方式共有三种:

1.完全路径匹配    :以 / 开始              例如:    /ServletDemo4 , /aaa/ServletDemo5 , /aaa/bbb/ServletDemo6

2.目录匹配       :以 / 开始 需要以 * 结束. 例如: /* ,/aaa/* ,/aaa/bbb/*

3.扩展名匹配     :不能以 / 开始 以 * 开始的. 例如: *.do , *.action

***** 错误的写法  : /*.do

有如下的配置:

<servlet>

<servlet-name>ServletDemo4</servlet-name>

<servlet-class>com.itheima.a_servlet.ServletDemo4</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletDemo4</servlet-name>

<url-pattern>/ServletDemo4</url-pattern>

</servlet-mapping>

<servlet>

<servlet-name>ServletDemo5</servlet-name>

<servlet-class>com.itheima.a_servlet.ServletDemo5</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletDemo5</servlet-name>

<url-pattern>/*</url-pattern>

</servlet-mapping>

<servlet>

<servlet-name>ServletDemo6</servlet-name>

<servlet-class>com.itheima.a_servlet.ServletDemo6</servlet-class>

</servlet>

<servlet-mapping>

<servlet-name>ServletDemo6</servlet-name>

<url-pattern>*.do</url-pattern>

</servlet-mapping>

如果访问地址:

http://localhost:8080/day09/ServletDemo4        :第一个

http://localhost:8080/day09/aaa.do              :第二个

***** 完全路径匹配 > 目录匹配 > 扩展名匹配

3.1.2.3  开发中的路径的编写:

  • 相对路径:都是需要找位置相对关系.不能以 / 开始的.

* ./ 当前路径  ../上一级目录

* 使用相对路径访问:

* http://localhost:8080/day09/demo4-url/demo1.html

* http://localhost:8080/day09/ServletDemo6

  • 绝对路径:不需要找位置相对关系. 以 / 开始的.

* 绝对路径中分为客户端路径和服务器端路径:

* 客户端路径一定要加工程名.     /day09/ServletDemo6

* 服务器端路径不需要加工程名.   /ServletDemo6

3.2      案例二:登录成功以后5秒钟跳转到另一个页面.

3.2.1    需求:

在登录成功后,页面5秒钟跳转到其他的一个页面.

3.2.2    分析:

3.2.2.1  技术分析:

【使用Http协议中的Refresh头信息】

Refresh之前已经介绍可以定时页面跳转.需要使用程序设置头信息才可以.

【response中设置响应头】

* addHeader(String name,String value);      --- 针对一个key对应多个value的响应头.

* addDateHeader(String name,long date);

* addIntHeader(String name,int value);

* setHeader(String name,String value);      --- 针对一个key对应一个value的响应头.

    * setDateHeader(String name,long date);

    * setIntHeader(String name,int value);

例如:头信息: xxx:aaa

* addHeader(“xxx”,”bbb”);        -->xxx:aaa,bbb

* setHeader(“xxx”,”bbb”);        -->xxx:bbb

3.2.3    代码实现:

在登录成功后的代码上,定时的跳转.

public class UserRefreshServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

try {

// 解决向页面输出中文的乱码问题!!!

response.setContentType("text/html;charset=UTF-8");

// 1.接收表单提交的参数.

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

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

// 2.封装到实体对象中.

User user = new User();

user.setUsername(username);

user.setPassword(password);

// 3.调用业务层处理数据.

UserService userService = new UserService();

User existUser = userService.login(user);

// 4.根据处理结果显示信息(页面跳转).

if(existUser == null){

// 登录失败

response.getWriter().println("<h1>登录失败:用户名或密码错误!~</h1>");

}else{

// 登录成功

// response.getWriter().println("Login Success...");

response.getWriter().println("<h1>登录成功!您好:"+existUser.getNickname()+"</h1>");

response.getWriter().println("<h3>页面将在5秒后跳转!</h3>");

response.setHeader("Refresh", "5;url=/day09/demo5-refresh/index.html");

}

} catch (Exception e) {

e.printStackTrace();

}

}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doGet(request, response);

}

}

3.2.4    总结:

3.2.4.1  使用JS控制读秒的效果.

<script type="text/javascript">

var time = 5;

window.onload = function(){

setInterval('changeTime()',1000);

}

function changeTime(){

time--;

document.getElementById("s1").innerHTML = time;

}

</script>

3.3      案例三:记录网站的登录成功的人数.

3.3.1    需求:

登录成功后,5秒后跳转到某个页面,在页面中显示您是第x位登录成功的用户.

3.3.2    分析:

3.3.2.1  技术分析:

【ServletContext对象】

***** ServletContext对象存取数据,存的数据都是有一定的作用的范围的.这种对象称为是域对象.

* 用来存取数据:

* 用来向ServletContext中存入数据.

* 用来从ServletContext中获取数据.

* 用来从ServletContext中移除数据.

3.3.3    代码实现:

/**

* 登录代码的Servlet

*/

public class UserCountServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

@Override

public void init() throws ServletException {

// 初始化一个变量count的值为0.

int count = 0;

// 将这个值存入到ServletContext中.

this.getServletContext().setAttribute("count", count);

}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

try {

response.setContentType("text/html;charset=UTF-8");

// 1.接收表单提交的参数.

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

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

// 2.封装到实体对象中.

User user = new User();

user.setUsername(username);

user.setPassword(password);

// 3.调用业务层处理数据.

UserService userService = new UserService();

User existUser = userService.login(user);

// 4.根据处理结果显示信息(页面跳转).

if(existUser == null){

// 登录失败

response.getWriter().println("<h1>登录失败:用户名或密码错误!</h1>");

}else{

// 登录成功

// 记录次数:

int count = (int) this.getServletContext().getAttribute("count");

count++;

this.getServletContext().setAttribute("count", count);

response.getWriter().println("<h1>登录成功:您好:"+existUser.getNickname()+"</h1>");

response.getWriter().println("<h3>页面将在5秒后跳转!</h3>");

response.setHeader("Refresh", "5;url=/day09/CountServlet");

}

} catch (Exception e) {

e.printStackTrace();

}

}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doGet(request, response);

}

}

public class CountServlet extends HttpServlet {

private static final long serialVersionUID = 1L;

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

// 获得Count的值。

response.setContentType("text/html;charset=UTF-8");

int count = (int) this.getServletContext().getAttribute("count");

response.getWriter().println("<h1>您是第"+count+"位登录成功的用户!</h1>");

}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

doGet(request, response);

}

}

3.3.4    总结:

3.3.4.1  ServletConfig:了解.获得Servlet的配置信息.

* String getServletName();      ---获得Servlet在web.xml中配置的name的值.

* String getInitParameter(String name); ---获得Servlet的初始化参数的.

* Enumeration getInitParameterNames();      ---获得所有Servlet的初始化参数的名称.

3.3.4.2  ServletContext:重要

ServletContext的作用:

* 1.用来获得全局初始化参数.

* 2.用来获得文件的MIME的类型.

* 3.作为域对象存取数据.

ServletContext是一个域对象.

    * 作用范围:整个web工程.

    * 创建:服务器启动的时候,tomcat服务器为每个web项目创建一个单独ServletContext对象.

    * 销毁:服务器关闭的时候,或者项目从服务器中移除的时候.

* 4.用来读取web项目下的文件.

web-day9的更多相关文章

  1. [红日安全]Web安全Day9 - 文件下载漏洞实战攻防

    本文由红日安全成员: Once 编写,如有不当,还望斧正. 大家好,我们是红日安全-Web安全攻防小组.此项目是关于Web安全的系列文章分享,还包含一个HTB靶场供大家练习,我们给这个项目起了一个名字 ...

  2. Web大前端面试题-Day9

    1. 请用至少3中方式实现数组去重? 方法一: indexOfvar arr1=[1,2,3,4,5,4,3,2,1];  function repeat1(arr){    for(var i=0, ...

  3. 撩课-Web大前端每天5道面试题-Day9

    1. 请用至少3中方式实现数组去重? 方法一: indexOf ,,,,,,,,]; function repeat1(arr){ ,arr2=[];i<arr.length;i++){ ){ ...

  4. C# Web应用调试开启外部访问

    在用C#开发Web应用时有个痛点,就是本机用VS开启Web应用调试时外部机器无法访问此Web应用.这里将会介绍如何通过设置允许局域网和外网机器访问本机的Web应用. 目录 1. 设置内网访问 2. 设 ...

  5. 网页提交中文到WEB容器的经历了些什么过程....

    先准备一个网页 <html><meta http-equiv="Content-Type" content="text/html; charset=gb ...

  6. 闲来无聊,研究一下Web服务器 的源程序

    web服务器是如何工作的 1989年的夏天,蒂姆.博纳斯-李开发了世界上第一个web服务器和web客户机.这个浏览器程序是一个简单的电话号码查询软件.最初的web服务器程序就是一个利用浏览器和web服 ...

  7. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

  8. 对抗密码破解 —— Web 前端慢 Hash

    (更新:https://www.cnblogs.com/index-html/p/frontend_kdf.html ) 0x00 前言 天下武功,唯快不破.但在密码学中则不同.算法越快,越容易破. ...

  9. 使用 Nodejs 搭建简单的Web服务器

    使用Nodejs搭建Web服务器是学习Node.js比较全面的入门教程,因为要完成一个简单的Web服务器,你需要学习Nodejs中几个比较重要的模块,比如:http协议模块.文件系统.url解析模块. ...

  10. 一步步开发自己的博客 .NET版(11、Web.config文件的读取和修改)

    Web.config的读取 对于Web.config的读取大家都很属性了.平时我们用得比较多的就是appSettings节点下配置.如: 我们对应的代码是: = ConfigurationManage ...

随机推荐

  1. c# 策略模式 加工厂模式-对象与行为分离

    计算器程序 策略模式是一种行为学模式.行为是同等级的算法  ,这些行为每个模式封装到一个类里 上端提供数据   ,下端提供算法 ,中间层context context  把上端的数据和算法  放到co ...

  2. C# Contains 包含空字符串的问题

    一个基本的条件判断,之前没有遇到,这次遇到后,感觉真是这些年白写程序了. if(("1,2,3").Contains("")) { MessageBox.Sho ...

  3. C++动态链接库

    1.动态链接库概述: 动态链接库通常都不能直接运行,也不能接受消息:只有在其他模块调用动态链接库中的函数时,它才发挥作用. Windows API中所有的函数都包含在动态链接库中. 动态链接库分静态库 ...

  4. BUG(0):用某位表示特定属性

    用某个bit表示特定属性通常有两种方式: 1.指定某个特定的value #define _PAGE_VALID 0x0001 0bit 为 1 时表示此时的page entry是有效的 用法如下,此时 ...

  5. 如果CocoaPods 导入的库需要修改代码

      如果经常要修改第三方框架的话,可以将需要修改的第三方库fork一份到自己的github,在里面做完修改之后,将podfile修改为: platform :ios, '7.0' pod '要导入的库 ...

  6. Python之线程与进程

    今天我们来了解一下Python的线程和进程的管理机制 首先,我们要了解下线程跟进程的概念: 线程(Thread)是操作系统能够进行运算调度的最小的单位,是一堆cpu的指令.他被包含在进程中,是进程中的 ...

  7. install virtualenv

    $ [sudo] pip install virtualenv $ mkdir ~/envs $ virtualenv ~/envs/lsbaws/ $ cd ~/envs/lsbaws/ $ ls ...

  8. 跟我学Spring Boot(二)Hello World

    1.打开DemoApplication添加如下代码 package com.example; import org.springframework.boot.SpringApplication; im ...

  9. mysql.lib 的使用

    一 说明      mysql.lib 提供了很多方便的操作,同时结合 vector 使用十分方便. 二 使用    1 声明需要用到的变量 static MYSQL s_mysql[DATABASE ...

  10. Python 中Lambda 表达式 实例解析

    Lambda 表达式 lambda表达式是一种简洁格式的函数.该表达式不是正常的函数结构,而是属于表达式的类型.而且它可以调用其它函数. 1.基本格式: lambda 参数,参数...:函数功能代码 ...