Spring 4.0为WebSocket通信提供了支持,包括:

  • 发送和接收消息的低层级API;
  • 发送和接收消息的高级API;
  • 用来发送消息的模板;
  • 支持SockJS,用来解决浏览器端、服务器以及代理不支持WebSocket的问题。

1 使用Spring的低层级WebSocket API

按照其最简单的形式,WebSocket只是两个应用之间通信的通道。位于WebSocket一端的应用发送消息,另外一端处理消息。因为它是全双工的,所以每一端都可以发送和处理消息。如图18.1所示。



WebSocket通信可以应用于任何类型的应用中,但是WebSocket最常见的应用场景是实现服务器和基于浏览器的应用之间的通信。

为了在Spring使用较低层级的API来处理消息,我们必须编写一个实现WebSocketHandler的类.WebSocketHandler需要我们实现五个方法。相比直接实现WebSocketHandler,更为简单的方法是扩展AbstractWebSocketHandler,这是WebSocketHandler的一个抽象实现。

public class MarcoHandler extends AbstractWebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(MarcoHandler.class);

    @Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.info("Received message: " + message.getPayload());
Thread.sleep(2000);
session.sendMessage(new TextMessage("Polo!"));
} }

除了重载WebSocketHandler中所定义的五个方法以外,我们还可以重载AbstractWebSocketHandler中所定义的三个方法:

  • handleBinaryMessage()
  • handlePongMessage()
  • handleTextMessage()

    这三个方法只是handleMessage()方法的具体化,每个方法对应于某一种特定类型的消息。

另外一种方案,我们可以扩展TextWebSocketHandler或BinaryWebSocketHandler。TextWebSocketHandler是AbstractWebSocketHandler的子类,它会拒绝处理二进制消息。它重载了handleBinaryMessage()方法,如果收到二进制消息的时候,将会关闭WebSocket连接。与之类似,BinaryWebSocketHandler也是AbstractWeb-SocketHandler的子类,它重载了handleTextMessage()方法,如果接收到文本消息的话,将会关闭连接。

现在,已经有了消息处理器类,我们必须要对其进行配置,这样Spring才能将消息转发给它。在Spring的Java配置中,这需要在一个配置类上使用@EnableWebSocket,并实现WebSocketConfigurer接口,如下面的程序清单所示。

程序清单18.2 在Java配置中,启用WebSocket并映射消息处理器

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer { @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// registry.addHandler(marcoHandler(), "/marco").withSockJS();
registry.addHandler(marcoHandler(), "/marco");
} @Bean
public MarcoHandler marcoHandler() {
return new MarcoHandler();
} }

或XML配置:

程序清单18.3 借助websocket命名空间以XML的方式配置WebSocket

不管使用Java还是使用XML,这就是所需的配置。

现在,我们可以把注意力转向客户端,它会发送“Marco!”文本消息到服务器,并监听来自服务器的文本消息。如下程序清单所展示的JavaScript代码开启了一个原始的WebSocket并使用它来发送消息给服务器。

程序清单18.4 连接到“marco” WebSocket的JavaScript客户端

通过发送“Marco!”,这个无休止的Marco Polo游戏就开始了,因为服务器端的MarcoHandler作为响应会将“Polo!”发送回来,当客户端收到来自服务器的消息后,onmessage事件会发送另外一个“Marco!”给服务器。这个过程会一直持续下去,直到连接关闭。

2 应对不支持WebSocket的场景

WebSocket是一个相对比较新的规范。虽然它早在2011年底就实现了规范化,但即便如此,在Web浏览器和应用服务器上依然没有得到一致的支持。Firefox和Chrome早就已经完整支持WebSocket了,但是其他的一些浏览器刚刚开始支持WebSocket。如下列出了几个流行的浏览器支持WebSocket功能的最低版本:

  • Internet Explorer:10.0
  • Firefox: 4.0(部分支持),6.0(完整支持)。
  • Chrome: 4.0(部分支持),13.0(完整支持)。
  • Safari: 5.0(部分支持),6.0(完整支持)。
  • Opera: 11.0(部分支持),12.10(完整支持)。
  • iOS Safari: 4.2(部分支持),6.0(完整支持)。
  • Android Browser: 4.4。

服务器端对WebSocket的支持也好不到哪里去。GlassFish在几年前就开始支持一定形式的WebSocket,但是很多其他的应用服务器在最近的版本中刚刚开始支持WebSocket。例如,我在测试上述例子的时候,所使用的就是Tomcat 8的发布候选构建版本。

即便浏览器和应用服务器的版本都符合要求,两端都支持WebSocket,在这两者之间还有可能出现问题。防火墙代理通常会限制所有除HTTP以外的流量。它们有可能不支持或者(还)没有配置允许进行WebSocket通信。

幸好,提到WebSocket的备用方案,这恰是SockJS所擅长的。SockJS让我们能够使用统一的编程模型,就好像在各个层面都完整支持WebSocket一样,SockJS在底层会提供备用方案。

例如,为了在服务端启用SockJS通信,我们在Spring配置中可以很简单地要求添加该功能。重新回顾一下程序清单18.2中的registerWebSocketHandlers()方法,稍微加一点内容就能启用SockJS:

    @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(marcoHandler(), "/marco").withSockJS();
}
  • XML完成相同的配置效果:

要在客户端使用SockJS,需要确保加载了SockJS客户端库。具体的做法在很大程度上依赖于使用JavaScript模块加载器(如require.js或curl.js)还是简单地使用<script>标签加载JavaScript库。加载SockJS客户端库的最简单办法是使用<script>标签从SockJS CDN中进行加载,如下所示:

<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>

除了加载SockJS客户端库以外,在程序清单18.4中,要使用SockJS只需修改两行代码:

var url = 'marco';
var sock = new SocktJS(url);

所做的第一个修改就是URL。SockJS所处理的URL是“http://”或“https://”模式,而不是“ws://”和“wss://”。即便如此,我们还是可以使用相对URL,避免书写完整的全限定URL。在本例中,如果包含JavaScript的页面位于“http://localhost:8080/websocket”路径下,那么给定的“marco”路径将会形成到“http://localhost:8080/websocket/marco”的连接。

3 使用STOMP消息

直接使用WebSocket(或SockJS)就很类似于使用TCP套接字来编写Web应用。因为没有高层级的线路协议(wire protocol),因此就需要我们定义应用之间所发送消息的语义,还需要确保连接的两端都能遵循这些语义。

不过,好消息是我们并非必须要使用原生的WebSocket连接。就像HTTP在TCP套接字之上添加了请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的线路格式(frame-based wire format)层,用来定义消息的语义。

乍看上去,STOMP的消息格式非常类似于HTTP请求的结构。与HTTP请求和响应类似,STOMP帧由命令、一个或多个头信息以及负载所组成。例如,如下就是发送数据的一个STOMP帧:

SEND
destination:/app/marco
content-length:20 {\"message\":\"Marco!\"}

 3.1 启用STOMP消息功能

在Spring MVC中为控制器方法添加@MessageMapping注解,使其处理STOMP消息,它与带有@RequestMapping注解的方法处理HTTP请求的方式非常类似。但是与@RequestMapping不同的是

  • @MessageMapping的功能无法通过@EnableWebMvc启用,而是@EnableWebSocketMessageBroker。
  • Spring的Web消息功能基于消息代理(message broker)构建,因此除了告诉Spring我们想要处理消息以外,还有其他的内容需要配置。
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/marcopolo").withSockJS();
} @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// registry.enableStompBrokerRelay("/queue", "/topic");
registry.enableSimpleBroker("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
} }

上述配置,它重载了registerStompEndpoints()方法,将“/marcopolo”注册为STOMP端点。这个路径与之前发送和接收消息的目的地路径有所不同。这是一个端点,客户端在订阅或发布消息到目的地路径前,要连接该端点。

WebSocketStompConfig还通过重载configureMessageBroker()方法配置了一个简单的消息代理。消息代理将会处理前缀为“/topic”和“/queue”的消息。除此之外,发往应用程序的消息将会带有“/app”前缀。图18.2展现了这个配置中的消息流。

启用STOMP代理中继

对于生产环境下的应用来说,你可能会希望使用真正支持STOMP的代理来支撑WebSocket消息,如RabbitMQ或ActiveMQ。这样的代理提供了可扩展性和健壮性更好的消息功能,当然它们也会完整支持STOMP命令。我们需要根据相关的文档来为STOMP搭建代理。搭建就绪之后,就可以使用STOMP代理来替换内存代理了,只需按照如下方式重载configureMessageBroker()方法即可:

  @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
  • 上述configureMessageBroker()方法的第一行代码启用了STOMP代理中继(broker relay)功能,并将其目的地前缀设置为“/topic”和“/queue”。这样的话,Spring就能知道所有目的地前缀为“/topic”或“/queue”的消息都会发送到STOMP代理中。

  • 在第二行的configureMessageBroker()方法中将应用的前缀设置为“/app”。所有目的地以“/app”打头的消息都将会路由到带有@MessageMapping注解的方法中,而不会发布到代理队列或主题中。

默认情况下,STOMP代理中继会假设代理监听localhost的61613端口,并且客户端的username和password均为“guest”。如果你的STOMP代理位于其他的服务器上,或者配置成了不同的客户端凭证,那么我们可以在启用STOMP代理中继的时候,需要配置这些细节信息:

  @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue", "/topic")
.setRelayHost("rabbit.someotherserver")
.setRelayPort(62623)
.setClientLogin("marcopolo")
.setClientPasscode("letmein01")
registry.setApplicationDestinationPrefixes("/app");
}

3.2 处理来自客户端的STOMP消息

Spring 4.0引入了@MessageMapping注解,它用于STOMP消息的处理,类似于Spring MVC的@RequestMapping注解。当消息抵达某个特定的目的地时,带有@MessageMapping注解的方法能够处理这些消息。

@Controller
public class MarcoController { private static final Logger logger = LoggerFactory
.getLogger(MarcoController.class); @MessageMapping("/marco")
public Shout handleShout(Shout incoming) {
logger.info("Received message: " + incoming.getMessage()); try { Thread.sleep(2000); } catch (InterruptedException e) {} Shout outgoing = new Shout();
outgoing.setMessage("Polo!"); return outgoing;
} }

示handleShout()方法能够处理指定目的地上到达的消息。在本例中,这个目的地也就是“/app/marco”(“/app”前缀是隐含的,因为我们将其配置为应用的目的地前缀)。

  • Shout类是个简单的JavaBean
public class Shout {

  private String message;

  public String getMessage() {
return message;
} public void setMessage(String message) {
this.message = message;
} }

因为我们现在处理的不是HTTP,所以无法使用Spring的HttpMessageConverter实现将负载转换为Shout对象。Spring 4.0提供了几个消息转换器,作为其消息API的一部分。表18.1描述了这些消息转换器,在处理STOMP消息的时候可能会用到它们。

表18.1 Spring能够使用某一个消息转换器将消息负载转换为Java类型

处理订阅

@SubscribeMapping的主要应用场景是实现请求-回应模式。在请求-回应模式中,客户端订阅某一个目的地,然后预期在这个目的地上获得一个一次性的响应。

例如,考虑如下@SubscribeMapping注解标注的方法:

  @SubscribeMapping({"/marco"})
public Shout handleSubscription(){
Shout outgoing = new Shout();
outgoing.setMessage("Polo!");
return outgoing;
}

可以看到,handleSubscription()方法使用了@SubscribeMapping注解,用这个方法来处理对“/app/marco”目的地的订阅(与@MessageMapping类似,“/app”是隐含的)。当处理这个订阅时,handleSubscription()方法会产生一个输出的Shout对象并将其返回。然后,Shout对象会转换成一条消息,并且会按照客户端订阅时相同的目的地发送回客户端。

如果你觉得这种请求-回应模式与HTTP GET的请求-响应模式并没有太大差别的话,那么你基本上是正确的。但是,这里的关键区别在于HTTPGET请求是同步的,而订阅的请求-回应模式则是异步的,这样客户端能够在回应可用时再去处理,而不必等待。

编写JavaScript客户端

程序清单18.7 借助STOMP库,通过JavaScript发送消息

在本例中,URL引用的是程序清单18.5中所配置的STOMP端点(不包括应用的上下文路径“/stomp”)。

但是,这里的区别在于,我们不再直接使用SockJS,而是通过调用Stomp.over(sock)创建了一个STOMP客户端实例。这实际上封装了SockJS,这样就能在WebSocket连接上发送STOMP消息。

3.3 发送消息到客户端

WebSocket通常视为服务器发送数据给浏览器的一种方式,采用这种方式所发送的数据不必位于HTTP请求的响应中。使用Spring和WebSocket/STOMP的话,该如何与基于浏览器的客户端通信呢?

Spring提供了两种发送数据给客户端的方法:

  • 作为处理消息或处理订阅的附带结果;
  • 使用消息模板。

在处理消息之后,发送消息

@MessageMapping("/marco")
public Shout handleShout(Shout incoming) {
logger.info("Received message: " + incoming.getMessage());
Shout outgoing = new Shout();
outgoing.setMessage("Polo!");
return outgoing;
}

当@MessageMapping注解标示的方法有返回值的时候,返回的对象将会进行转换(通过消息转换器)并放到STOMP帧的负载中,然后发送给消息代理。

默认情况下,帧所发往的目的地会与触发处理器方法的目的地相同,只不过会添加上“/topic”前缀。就本例而言,这意味着handleShout()方法所返回的Shout对象会写入到STOMP帧的负载中,并发布到“/topic/marco”目的地。不过,我们可以通过为方法添加@SendTo注解,重载目的地:

@MessageMapping("/marco")
@SendTo("/topic/shout")
public Shout handleShout(Shout incoming) {
logger.info("Received message: " + incoming.getMessage());
Shout outgoing = new Shout();
outgoing.setMessage("Polo!");
return outgoing;
}

按照这个@SendTo注解,消息将会发布到“/topic/shout”。所有订阅这个主题的应用(如客户端)都会收到这条消息。

按照类似的方式,@SubscribeMapping注解标注的方式也能发送一条消息,作为订阅的回应。

  @SubscribeMapping("/marco")
public Shout handleSubscription(){
Shout outgoing = new Shout();
outgoing.setMessage("Polo!");
return outgoing;
}

@SubscribeMapping的区别在于这里的Shout消息将会直接发送给客户端,而不必经过消息代理。如果你为方法添加@SendTo注解的话,那么消息将会发送到指定的目的地,这样会经过代理。

在应用的任意地方发送消息

@MessageMapping和@SubscribeMapping提供了一种很简单的方式来发送消息,这是接收消息或处理订阅的附带结果。不过,Spring的SimpMessagingTemplate能够在应用的任何地方发送消息,甚至不必以首先接收一条消息作为前提。

我们不必要求用户刷新页面,而是让首页订阅一个STOMP主题,在Spittle创建的时候,该主题能够收到Spittle更新的实时feed。在首页中,我们需要添加如下的JavaScript代码块:

Handlebars库将Spittle数据渲染为HTML并插入到列表中。Handlebars模板定义在一个单独的<script>标签中,如下所示:

在服务器端,我们可以使用SimpMessagingTemplate将所有新创建的Spittle以消息的形式发布到“/topic/spittlefeed”主题上。如下程序清单展现的SpittleFeedServiceImpl就是实现该功能的简单服务:

程序清单18.8 SimpMessagingTemplate能够在应用的任何地方发布消息

@Service
public class SpittleFeedServiceImpl implements SpittleFeedService { private SimpMessageSendingOperations messaging; @Autowired
public SpittleFeedServiceImpl(SimpMessageSendingOperations messaging) {
this.messaging = messaging;
} public void broadcastSpittle(Spittle spittle) {
messaging.convertAndSend("/topic/spittlefeed", spittle);
} }

在这个场景下,我们希望所有的客户端都能及时看到实时的Spittle feed,这种做法是很好的。但有的时候,我们希望发送消息给指定的用户,而不是所有的客户端。

4 为目标用户发送消息

但是,如果你知道用户是谁的话,那么就能处理与某个用户相关的消息,而不仅仅是与所有客户端相关联。好消息是我们已经了解了如何识别用户。通过使用与第9章相同的认证机制,我们可以使用Spring Security来认证用户,并为目标用户处理消息。

在使用Spring和STOMP消息功能的时候,我们有三种方式利用认证用户:

  • @MessageMapping和@SubscribeMapping标注的方法能够使用Principal来获取认证用户;
  • @MessageMapping、@SubscribeMapping和@MessageException方法返回的值能够以消息的形式发送给认证用户;
  • SimpMessagingTemplate能够发送消息给特定用户。

4.1 在控制器中处理用户的消息

在控制器的@MessageMapping或@SubscribeMapping方法中,处理消息时有两种方式了解用户信息。在处理器方法中,通过简单地添加一个Principal参数,这个方法就能知道用户是谁并利用该信息关注此用户相关的数据。除此之外,处理器方法还可以使用@SendToUser注解,表明它的返回值要以消息的形式发送给某个认证用户的客户端(只发送给该客户端)。

  @MessageMapping("/spittle")
@SendToUser("/queue/notifications")
public Notification handleSpittle(Principal principal, SpittleForm form) {
Spittle spittle = new Spittle(principal.getName(), form.getText(), new Date());
spittleRepo.save(spittle);
feedService.broadcastSpittle(spittle);
return new Notification("Saved Spittle for user: " + principal.getName());
}

JavaScript客户端代码:

stomp.subscribe("/user/queue/notifications", handleNotification);

在内部,以“/user”作为前缀的目的地将会以特殊的方式进行处理。这种消息不会通过AnnotationMethodMessageHandler(像应用消息那样)来处理,也不会通过SimpleBrokerMessageHandler或StompBrokerRelayMessageHandler(像代理消息那样)来处理,以“/user”为前缀的消息将会通过UserDestinationMessageHandler进行处理,如图18.4所示。

!!!

4.2 为指定用户发送消息

除了convertAndSend()以外,SimpMessagingTemplate还提供了convertAndSendToUser()方法。按照名字就可以判断出来,convertAndSendToUser()方法能够让我们给特定用户发送消息。

为了阐述该功能,我们要在Spittr应用中添加一项特性,当其他用户提交的Spittle提到某个用户时,将会提醒该用户。例如,如果Spittle文本中包含“@jbauer”,那么我们就应该发送一条消息给使用“jbauer”用户名登录的客户端。如下程序清单中的broadcastSpittle()方法使用了convertAndSendToUser(),从而能够提醒所谈论到的用户。

@Service
public class SpittleFeedServiceImpl implements SpittleFeedService { private SimpMessagingTemplate messaging;
private Pattern pattern = Pattern.compile("\\@(\\S+)"); @Autowired
public SpittleFeedServiceImpl(SimpMessagingTemplate messaging) {
this.messaging = messaging;
} public void broadcastSpittle(Spittle spittle) {
messaging.convertAndSend("/topic/spittlefeed", spittle); Matcher matcher = pattern.matcher(spittle.getMessage());
if (matcher.find()) {
String username = matcher.group(1);
messaging.convertAndSendToUser(username, "/queue/notifications",
new Notification("You just got mentioned!"));
}
} }

在broadcastSpittle()中,如果给定Spittle对象的消息中包含了类似于用户名的内容(也就是以“@”开头的文本),那么一个新的Notification将会发送到名为“/queue/notifications”的目的地上。因此,如果Spittle中包含“@jbauer”的话,Notification将会发送到“/user/jbauer/queue/notifications”目的地上。

5 处理消息异常

源码

https://github.com/myitroad/spring-in-action-4/tree/master/Chapter_18

第18章-使用WebSocket和STOMP实现消息功能的更多相关文章

  1. springboot支持webSocket和stomp实现消息订阅通知示例

    先导入支持websocket的jar包,这里用Gradle构建的项目: dependencies { compile('org.springframework.boot:spring-boot-sta ...

  2. 第18章 集合框架(2)-Set接口

    第18章 集合框架(2)-Set接口 Set是Collection子接口,模拟了数学上的集的概念 Set集合存储特点 1.不允许元素重复 2.不会记录元素的先后添加顺序 Set只包含从Collecti ...

  3. Java 第18章 多态

    18 章  --> 多态 继承: extends 抽象类 abstract (限制类的实例化) 抽象方法 public abstract void show(); //抽象方法只有方法的声明,没 ...

  4. LPTHW 笨方法学python 18章

    看完18章以后,发现第一个练习中,使用了*args读取全部的的输入参数作为一个元组,但是在他的练习中只给了两个变量去赋值,当用户不清楚这个函数的定义时,就可能会给出过多的变量进这个函数,那么就会出现如 ...

  5. 《TCP/IP详解卷1:协议》第17、18章 TCP:传输控制协议(1)-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  6. 《TCP/IP详解卷1:协议》第17、18章 TCP:传输控制协议(2)-读书笔记

    章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP ...

  7. Linux就这个范儿 第18章 这里也是鼓乐笙箫 Linux读写内存数据的三种方式

    Linux就这个范儿 第18章  这里也是鼓乐笙箫  Linux读写内存数据的三种方式 P703 Linux读写内存数据的三种方式 1.read  ,write方式会在用户空间和内核空间不断拷贝数据, ...

  8. 【C#4.0图解教程】笔记(第9章~第18章)

    第9章 语句 1.标签语句 ①.标签语句由一个标识符后面跟着一个冒号再跟着一条语句组成 ②.标签语句的执行完全如同标签不存在一样,并仅执行冒号后的语句. ③.给语句添加一个标签允许控制从代码的另一部分 ...

  9. 第18章 备忘录模式(Memento Pattern)

    原文  第18章 备忘录模式(Memento Pattern) 备忘录模式       概述: 备忘录模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Toke ...

随机推荐

  1. Redis 字符串(String)

    Redis 字符串(String) Redis 字符串数据类型的相关命令用于管理 redis 字符串值,基本语法如下: 语法 redis 127.0.0.1:6379> COMMAND KEY_ ...

  2. Maven打包同一个jar有不同的:版本+时间戳(解决思路)

    在我们的开发过程中,目前流行的版本控制工具maven,在项目开发阶段,大家都是通过发布SNAPSHOT快照版进行相互模块之间的依赖开发, 这个时候就会有一个问题,要是一天构建多次的快照版,会发现在项目 ...

  3. 区域存储网络(SAN)与 网络直接存储(NAS)

    随着互联网及网络应用的飞速发展,数据信息存储系统所需处理的数据类型也呈爆炸性增长,这使数据信息存储系统面临前所未有的挑战.附加式网络存储装置(Network Attached Storage,缩写为N ...

  4. 关于bonecp和QuerRunner

    之前一直以为boneCP和QueryRunner是绑定的,但是其实不是,后者来自于commons-dbUtils,BoneCP就是负责连接池. while preparing SQL: UPSERT ...

  5. PL/SQL本地远程连接数据库

    记录自己在开发中只用一次,但是容易忘记的问题,PL/SQL-ORACLE配置远程数据库访问: 1,下载PL/SQL连接工具,链接: https://pan.baidu.com/s/1kVeeLNp 密 ...

  6. MVC中Ajax post 和Ajax Get——提交对象Model

    HTTP 请求:GET vs. POST两种在客户端和服务器端进行请求-响应的常用方法是:GET 和 POST.GET - 从指定的资源请求数据POST - 向指定的资源提交要处理的数据GET 基本上 ...

  7. 0004-程序流程2之ui-router大意

    按照传统的操作方式,一般是点击某个按钮或者某个菜单项,我们将页面通过指定URL的方式跳转, 在HTML中,使用的是传统的a标签的href属性作跳转,在使用ui-router的情况下,我们对一个按钮 添 ...

  8. AngryFuzz3r-web扫描工具

    项目地址:https://github.com/ihebski/angryFuzzer 下载完后打开文件 安装一下所需的Python模块 root@sch01ar:/sch01ar/angryFuzz ...

  9. shell编程之sed编辑器&gawk程序

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://twentyfour.blog.51cto.com/945260/560372 s ...

  10. Python之面向过程和面向对象的区别

    一.面向过程 1.面向过程:核心是过程二字,过程指的是解决问题的步骤,好比如设计一条流水线,是一种机械式的思维方式. 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 .基本设计思路就是 ...