SpringBoot——Web开发

SpringBoot——Web开发

      • 一、SpringBoot对静态资源的映射规则
        • 1、webjars的方式
        • 2、/**
        • 3、首页映射
        • 4、网页图标
        • 5、静态资源属性配置
      • 二、模板引擎
        • 1、引入Thymeleaf
        • 2、Thymeleaf的使用
      • 三、SpringMVC自动配置
        • 1、SpringBoot为SpringMVC提供的自动配置
        • 2、SpringMVC的扩展配置
      • 四、Restful CRUD
        • 1、配置首页
        • 2、国际化配置
        • 3、 登录拦截器
        • 4、日期格式化
      • 五、错误处理机制
        • 1、SpringBoot默认的错误处理机制
        • 2、定制错误页面
        • 3、定制错误json数据
          • (1)自定义异常处理
          • (2)自适应自定义异常处理
      • 六、配置嵌入式的Servlet容器
        • 1、定制和修改Servlet容器的相关配置
        • 2、注册Servlet三大组件——Servlet、Filter、Listener
        • 3、替换为其他嵌入式Servlet容器
        • 4、嵌入式Servlet容器自动配置原理
        • 5、嵌入式Servlet容器启动原理
        • 6、使用外置的Servlet容器

详情可参考官方文档:SpringBoot2.4.0开发WEB应用官方文档

一、SpringBoot对静态资源的映射规则

addResourceHandlers:添加资源映射
SpringBoot——Web开发_第1张图片

1、webjars的方式

所有的请求/webjars/** ,都去classpath:/META-INF/resources/webjars/ 找资源;使用webjars可以用jar包的方式引入静态资源:http://www.webjars.org/,想使用什么静态资源,导入对应的依赖就可以了。
SpringBoot——Web开发_第2张图片
例如:利用webjars访问jquery下的静态资源

localhost:8080/webjars/jquery/3.3.1/jquery.js

当然前提是需要导入jquery的webjars依赖:

<dependency>
    <groupId>org.webjarsgroupId>
    <artifactId>jqueryartifactId>
    <version>3.5.1version>
dependency>

2、/**

访问当前项目的任何资源,都去静态资源的文件夹找映射关系。

"classpath:/META‐INF/resources/", 
"classpath:/resources/",
"classpath:/static/", 
"classpath:/public/" 
"/":当前项目的根路径

SpringBoot——Web开发_第3张图片

3、首页映射

静态资源文件夹下的所有index.html页面都被"/**"映射;

SpringBoot——Web开发_第4张图片

4、网页图标

所有的**/favicon.ico 都是在静态资源文件下寻找,但是在SpringBoot2.4.0中没有这一项了。

5、静态资源属性配置

可以设置和静态资源有关的参数,缓存时间等,例如可以自定义静态资源的位置,定义之后,默认的静态资源位置将会失效。

spring.resources.static-locations=classpath:/hello/,classpath:/glp/

SpringBoot——Web开发_第5张图片

二、模板引擎

纯静态的页面开发会很麻烦,SpringBoot中使用模板引擎。模板引擎主要有:
JSP、Velocity、Freemarker、Thymeleaf等。它们都有着共同的思想:模板引擎负责将数据填入模板中的表达式中。
SpringBoot——Web开发_第6张图片
SpringBoot推荐使用Thymeleaf模板引擎。

1、引入Thymeleaf

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

springBoot2.4.0提供的Thymeleaf默认版本是3.0.11
SpringBoot——Web开发_第7张图片

2、Thymeleaf的使用

(1)thymeleaf的属性配置文件
SpringBoot——Web开发_第8张图片
属性配置文件中有一个默认的前缀和后缀,只要我们把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染了。

(2)Thymeleaf的语法

参考Thymeleaf的官方文档:https://www.thymeleaf.org/

1)导入名称空间
使用时需要导入 thymeleaf的名称空间,导入之后将会有Thymeleaf的语法提示:

<html lang="en" xmlns:th="http://www.thymeleaf.org">

2)简单测试

@RequestMapping("/t1")
public String test1(Model model){
   //存入数据
   model.addAttribute("hello","HelloWorld");
   //classpath:/templates/test.html
   return "test";
}


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF‐8">
    <title>Titletitle>
head>
<body>
    <h1>成功!h1>
    <!‐‐th:text 将div里面的文本内容设置为 ‐‐>
    <div th:text="${hello}">这是显示欢迎信息div>
body>
html>

注意:
开发期间模板引擎页面修改以后,要实时生效

  • 模板引擎都有缓存,我们需要禁用模板引擎的缓存:
spring.thymeleaf.cache=false
  • 页面修改完成以后ctrl+f9:重新编译
  • 在使用SrpingBoot2.4.4版本时,也是可以直接引入依赖的,其SpringBoot版本控制中的版本为:
 <thymeleaf.version>3.0.12.RELEASE</thymeleaf.version>

奈何在开始布置项目时,使用thymleaf访问模板老是报404。在百般测试之下,最后将本地仓库全部清空,然后重新加载依赖,最后成功解决。这应该是下载过程中网速不好,依赖包损坏的问题。

3)语法标签
th:text:改变当前元素里面的文本内容
th:任意html属性:可以替换原生属性的值
SpringBoot——Web开发_第9张图片
注意:
[ [ ] ]等价于th:text[ ( ) ]等价于th:utext

4)表达式语法

${...}:获取变量值

  • 获取对象的属性、调用方法
  • 使用内置的基本对象
  • 使用内置的工具对象

*{...}:选择表达式:和${}在功能上一样

  • 补充功能:配合 th:object="${session.user}:来使用。

#{...}:获取国际化内容

@{...}:定义URL

th:href=@{/order/process(execId=${execId},execType='FAST')}

~{...}:片段引用表达式

⑥ 字面量

⑦ 文本操作

⑧ 数学运算:+ , ‐ , * , / , %

⑨ 布尔运算:and , or,! , not

⑩ 比较运算:> , < , >= , <=(gt , lt , ge , le)

11 条件运算:(if) ? (then) : (else)

12 _:缺省:(if) ? (then) : _

5)简单应用

@RequestMapping("/t2")
public String test2(Map<String,Object> map){
   //存入数据
   map.put("msg","

Hello

"
); map.put("users", Arrays.asList("cbj","glp")); return "test"; }

<h4 th:each="user :${users}" th:text="${user}">h4>

<h4>
   
   <span th:each="user:${users}">[[${user}]]span>
h4>

6)公共页面抽取
将某页面的一部分加入th:fragment,来抽取公共片段。

<div th:fragment="copy">
  <h1>hello</h1>
</div>

在其它的html中使用th:insert引入公共片段

<div th:insert="~{footer :: copy}"></div>

其中:footer为模板名,copy为抽取的片段名。

三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中

三种引入方式举例:

抽取片段:
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
footer>

引入方式:
<div th:insert="footer :: copy">div>
<div th:replace="footer :: copy">div>
<div th:include="footer :: copy">div>
 
效果:
<div>
    <footer>
    © 2011 The Good Thymes Virtual Grocery
    footer>
div>
 
<footer>
© 2011 The Good Thymes Virtual Grocery
footer>
 
<div>
© 2011 The Good Thymes Virtual Grocery
div>

注意:

  • 如果使用th:insert等属性进行引入,可以不用写~{}:行内写法可以加上:[[~{}]];[(~{})]
  • 除了为片段定义模板名,还可以用id名来代替模板名。
	<div th:insert="~{dashboard::#sidebar}"></div>

7)引入片段时传入参数
在抽取的公共片段中做判断,然后在不同的引入页面传入不同的参数,可以对更改不同页面的参数,比如文字高亮的显示。
公共片段:
SpringBoot——Web开发_第10张图片
在不同页面引入时传递参数:
在这里插入图片描述

8)日期格式化

th:text="${#dates.format(emp.birth, 'yyyy/MM/dd HH:mm')}"

三、SpringMVC自动配置

1、SpringBoot为SpringMVC提供的自动配置

Spring Boot为Spring MVC提供了自动配置。以下是SpringBoot对SpringMVC的自动配置:

1)配置了视图解析器:ContentNegotiatingViewResolver和BeanNameViewResolver

  • ContentNegotiatingViewResolver:组合所有的视图解析器,可以将我们自定义的视图解析器组合起来。

2)提供静态资源的支持,包括对WebJars的支持。

3)自动注入了转换器Converter、GenericConverter和格式化器Formatter。

  • Formatter:日期格式化器等,可以自定义Formatter

4)对HttpMessageConverters的支持。

  • HttpMessageConverters用来转换HTTP的请求和响应。user->json

5) MessageCodesResolver的自动注册。

  • MessageCodesResolver定义错误代码的生成规则

6)静态index . html的支持。

7)自动使用ConfigurableWebBindingInitializer

  • 初始化web数据绑定器,web数据绑定器功能就是将请求数据绑定到javabean中。

注意:

  • SpringBoot在自动配置组件的时候,会先看容器中有没有用户自己配置的(@Bean、@Component)如果有,就用用户配置的,如果没有,就使用默认的自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;

2、SpringMVC的扩展配置

(1)扩展配置的使用

如果您想要保留那些SpringBoot MVC定制并进行更多的MVC定制(拦截器、格式化器、视图控制器和其他特性),您可以添加自己的WebMvcConfigurer类型的一个配置类,但是不能加@EnablewebMvc。

下面我们添加一个视图跳转测试一下:

@Configuration
public class webmvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/hello1").setViewName("test");
    }
}

在做其它自动配置时,会导入EnableWebMvcConfiguration:
SpringBoot——Web开发_第11张图片
在这里插入图片描述

在EnableWebMvcConfiguration 的父类DelegatingWebMvcConfiguration 中:
SpringBoot——Web开发_第12张图片
举例:将所有的WebMvcConfigurer相关配置都来一起调用
SpringBoot——Web开发_第13张图片

(2)全面接管SpringMVC
在配置类中添加@EnableWebMvc,所有的SpringMVC的自动配置就失效了

@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
 
}

原因:
@EnableWebMvc将WebMvcConfigurationSupport组件导入进来;
导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能;
SpringBoot——Web开发_第14张图片
注意:
在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置

四、Restful CRUD

1、配置首页

配置页面跳转:

@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {

    @Bean 
    public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){
        WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName("login");
                registry.addViewController("/index.html").setViewName("login");
            }
        };
        return adapter;
    }
}

2、国际化配置

(1)编写国际化配置文件i18n
i18n的由来:internationalization

当编写国际化配置文件时,idea会自动识别国际化配置文件:
建立一个login.properties文件,还有一个login_zh_CN.properties;发现IDEA自动识别了我们要做国际化操作,文件夹变了。
SpringBoot——Web开发_第15张图片

SpringBoot——Web开发_第16张图片
(2)ResourceBundleMessageSource
SpringBoot在MessageSourceAutoConfiguration中自动配置好了管理国际化资源文件的组件

SpringBoot——Web开发_第17张图片
寻找国际化配置文件,使用spring.messages.basename指定的基础名,或者使用默认的类路径下的messages作为基础名。(基础名为去掉语言国家代码的名称):

spring.messages.basename=i18n.login //login即为基础名

(3)获取国际化配置

SpringBoot——Web开发_第18张图片
注意:
对于input这种没有内容体的,我们需要使用[ [] ]来代替th:text

LocaleResolver(获取区域信息对象):

SpringBoot——Web开发_第19张图片

在连接中携带区域信息:
SpringBoot——Web开发_第20张图片
重写区域解析器:

public class MyLocaleResolver implements LocaleResolver {
    
    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        String l = request.getParameter("l");
        Locale locale = Locale.getDefault();
        if(!StringUtils.isEmpty(l)){
            String[] split = l.split("_");
            locale = new Locale(split[0],split[1]);
        }
        return locale;
    }
 
    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale 
locale) {
 
    }
}

将解析器注入到容器中:

 @Bean
    public LocaleResolver localeResolver(){
        return new MyLocaleResolver();
    }
}

注意:
这里解析器的方法名要和mvc自动配置类中的方法名相同,必须为localeResolver(),这样相当于与原来解析器的id名相同,可以替换掉原来的解析器。
SpringBoot——Web开发_第21张图片

3、 登录拦截器

(1)LoginController
SpringBoot——Web开发_第22张图片
登录时可以向前端传递错误信息:

<p style="color: red" th:text="${msg}"
 th:if="${not #strings.isEmpty(msg)}"></p>

(2) HandlerIntercepter
SpringBoot——Web开发_第23张图片
(3)注册拦截器
SpringBoot——Web开发_第24张图片
注:
springBoot 2.xx后,配置拦截器时,静态资源也会被拦截,所以此处需要排除静态资源。

4、日期格式化

前端添加日期数据时,可以改变日期格式化的方式:

spring.mvc.date-format=yyyy-MM-dd

五、错误处理机制

1、SpringBoot默认的错误处理机制

(1)错误页面的默认效果

对于一个错误的请求,浏览器会返回一个错误页面:
SpringBoot——Web开发_第25张图片

对于其他客户端,默认响应一个json数据:
SpringBoot——Web开发_第26张图片

(2)ErrorMvcAutoConfiguration——错误处理自动配置

默认给容器中添加了如下组件:

  • DefaultErrorAttributes
  • basicErrorController
  • errorPageCustomizer
  • DefaultErrorViewResolver

错误处理流程:

1)系统出现4xx或者5xx之类的错误时,ErrorPageCustomizer负责定制错误的响应规则,会根据error.path制定的路径去请求错误页面,默认是进行/error请求。
在这里插入图片描述

2)BasicErrorController负责处理/error请求:
SpringBoot——Web开发_第27张图片
BasicErrorController中有两种请求的处理方式:
一个产生html数据,一个产生json数据。
SpringBoot——Web开发_第28张图片
注意:
BasicErrorController如何选择请求的处理方式是根据请求头中的Accept进行判断的:

① 浏览器的请求头为text/html
SpringBoot——Web开发_第29张图片
② 客户端的请求头为:*/*
SpringBoot——Web开发_第30张图片
3)查找错误视图解析器
BasicErrorController中对于浏览器请求的处理中,resolveErrorView负责解析所有的错误视图解析器:
SpringBoot——Web开发_第31张图片
而DefaultErrorViewResolver正是默认的错误视图解析器,也会被resolveErrorView解析。而DefaultErrorViewResolver决定了跳转到哪个错误页面。默认SpringBoot可以去找 error文件夹下的4xx或者5xx页面。

SpringBoot——Web开发_第32张图片

2、定制错误页面

(1)有模板引擎的情况下
error/状态码:将错误页面命名为错误状态码.html 放在模板引擎文件夹里面的error文件夹下,发生此状态码的错误就会来到对应的页面;

注:
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);

DefaultErrorAttributes负责为视图封装model属性
SpringBoot——Web开发_第33张图片
封装的属性如下:

  • timestamp:时间戳
  • status:状态码
  • error:错误提示
  • exception:异常对象
  • message:异常消息
  • errors:JSR303数据校验的错误都在这里

测试:
在template页面下创建error/404
SpringBoot——Web开发_第34张图片

(2)没有模板引擎(模板引擎找不到这个错误页面),就会去静态资源文件夹下找;

(3)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
SpringBoot——Web开发_第35张图片

3、定制错误json数据

(1)自定义异常处理

在这里插入图片描述
利用ExceptionHander进行j=异常捕获,可以返回自定义的json数据。但是此时客户端和浏览器收到的都是json数据,那么如何让浏览器收到html收据,而客户端收到json数据呢?

(2)自适应自定义异常处理

DefaultErrorAttributes中关于状态码的获取是通过一个Attribute来获取的:
在这里插入图片描述

@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(UserNotExistException.class)
    public String handlerException(Exception e, WebRequest request){
		//0代表请求域,1代表session域  
      request.setAttribute("javax.servlet.error.status_code",503,0);
        Map<String,Object> map = new HashMap<>();
        map.put("code:","User not Exists");
        map.put("message:",e.getMessage());

        request.setAttribute("ext",map,0);
        return "forward:/error";
    }

通过重定向到/error,让系统来处理html数据或者json数据,实现自动分发数据。

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes(AbstractErrorController规定的方法)得到的。

SpringBoot中配置了默认的ErrorAttributes:即默认使用DefaultErrorAttributes.getErrorAttributes()进行数据处理
SpringBoot——Web开发_第36张图片
DefaultErrorAttributes中默认配置了时间戳等属性:
SpringBoot——Web开发_第37张图片
我们可以通过自定义一个DefaultErrorAttributes来设置我们需要的属性:

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        map.put("company","三味书屋");
        
        Map<String,Object> ext = (Map<String,Object>)webRequest.getAttribute("ext", 0);
        map.put("ext",ext);
        return map;
    }
}

注:
我们可以通过webRequest来获取handlerException中定义的错误信息。

六、配置嵌入式的Servlet容器

在SpringBoot之前,如果我们要发布项目,需要在外部配置好tomcat环境。而在springBoot中,我们已经嵌入了tomcat。‘

查看pom文件的依赖图:Springboot默认使用tomcat作为servlet容器。
SpringBoot——Web开发_第38张图片

1、定制和修改Servlet容器的相关配置

(1)利用ServerProperties配置来修改和server有关的配置
SpringBoot——Web开发_第39张图片
可以进行的配置:
SpringBoot——Web开发_第40张图片
(2)WebServerFactoryCustomizer(Servlet容器的定制器)

使用定制器来修改Servlet容器的配置,定制器都在org.springframework.boot.web.server包下:

@Configuration
public class MyServerConfig {

 @Bean
public WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory> myServerCustomizer(){
 return new WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>() {
            @Override
            public void customize(ConfigurableTomcatWebServerFactory factory) {
                factory.setPort(8086);
            }
        };
    }
}

配置生效了:
在这里插入图片描述

2、注册Servlet三大组件——Servlet、Filter、Listener

SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器,没有web.xml文件,那么注册三大组件需要用以下方式:

  • ServletRegistrationBean :配置servlet
  • FilterRegistrationBean: 配置Filter过滤器
  • ServletListenerRegistrationBean: 配置Listener监听器

(1)注册组件ServletRegistrationBean
SpringBoot——Web开发_第41张图片
(2)注册组件FilterRegistrationBean
SpringBoot——Web开发_第42张图片
(3)ServletListenerRegistrationBean

拿启动和销毁的监听器ServletContextListener举例:
SpringBoot——Web开发_第43张图片

(4)DispatcherServletAutoConfiguration中的配置
DispatcherServletAutoConfiguration中通过DispatcherServletRegistrationBean配置拦截路径:
SpringBoot——Web开发_第44张图片
注:
我们可以在配置文件中spring.mvc.servlet.path=/来配置dispatcherServlet的默认拦截路径。/默认拦截除jsp外的所有文件,/*拦截所有请求。

3、替换为其他嵌入式Servlet容器

SpringBoot2.xx支持的嵌入Servlet容器有以下四个:

  • jetty:支持长连接,适合做聊天系统
  • netty:是一个基于NIO的客户、服务器端的编程框架
  • tomcat:
  • undertow:不支持JSP,但是并发性能好

SpringBoot——Web开发_第45张图片
SpringBoot默认是支持tomcat的,如果要切换servlet容器,那么需要先将tomcat排除掉:
SpringBoot——Web开发_第46张图片
然后再pom.xml中加入所需容器的依赖:
SpringBoot——Web开发_第47张图片
以jetty启动了:
在这里插入图片描述

4、嵌入式Servlet容器自动配置原理

在 springboot2.x 版本中,通过EmbeddedWebServerFactoryCustomizerAutoConfiguration自动创建对应的WebServerFactoryCustomizer来定制servlet容器。

org.springframework.boot.autoconfigure
.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration

SpringBoot根据导入的依赖情况,给容器中添加相应的XXXXWebServerFactoryCustomizerConfiguration

SpringBoot——Web开发_第48张图片

参考博客:https://blog.csdn.net/weixin_44414492/article/details/111160732

5、嵌入式Servlet容器启动原理

通过断点来查看执行顺序,下面是断点的执行顺序:
SpringBoot——Web开发_第49张图片

(1)SpringBoot应用启动运行run方法
(2)运行run方法会创建并初始化IOC容器对象

创建对应类型的web容器,如果是web类型则创建web类型的容器:
SpringBoot——Web开发_第50张图片
初始化IOC容器:
SpringBoot——Web开发_第51张图片

getWebServerFactory()通过名称匹配最终能够获取到一个与当前应用所导入的Servlet类型(我们导入了Tomcat依赖)相匹配的web服务工厂,通过工厂就可以获取到相应的 WebServerFactoryCustomizer (Web服务工厂定制器)

(3) createWebServer()执行后,

  • createWebServer()执行后,创建了获取到一个与当前应用所导入的Servlet类型(我们导入了Tomcat依赖)相匹配的web服务工厂,本例中为tomcatServletWebServerFactory
  • WebServerFactoryCustomizerBeanPostProcessor(web服务工厂定制器组件的后置处理器),从容器中获取所有的定制器(WebServerFactoryCustomizer)来定制Servlet容器的相关配置;

SpringBoot——Web开发_第52张图片
通过后置处理器获取到的TomcatWebServerFactoryCustomizer调用customize()定制方法,获取到Servlet容器相关配置类ServerProperties,进行自动配置。

6、使用外置的Servlet容器

(1)嵌入式Servlet容器:应用可以打成可执行的jar

  • 优点:简单、便携;
  • 缺点:默认不支持JSP、优化定制比较复杂(使用定制器WebServerFactoryCustomizer);

(2)外置的Servlet容器:

  • 外面安装Tomcat—应用war包的方式打包;

(3)使用外部Servlet容器的步骤:

  • 必须创建一个war项目
    SpringBoot——Web开发_第53张图片
  • 既然是以war包的形式,那么需要WEB-INF文件夹,搭建好目录结构

SpringBoot——Web开发_第54张图片

  • 将tomcat服务器整合到项目中
    SpringBoot——Web开发_第55张图片

  • 创建项目后,嵌入式的Tomcat指定为provided;

SpringBoot——Web开发_第56张图片

  • 项目创建后,多了一个ServletInitializer类

SpringBoot——Web开发_第57张图片

服务器启动时会加载ServletInitializer,而ServletInitializer通过重写configure方法,来告诉SpringBoot主程序DemoApplication在哪。

  • 启动服务器即可

启动原理:

jar包:执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器;

war包:先启动服务器,服务器再启动SpringBoot应用,最后启动ioc容器;

你可能感兴趣的:(SpringBoot,spring,java,web)