不要在HTTP的GET请求里携带Request Body

2017 Nov 30

在设计RESTful service时,可不可以在一个GET请求中携带body? 答案是:最好不要这些做。

有一些RESTful service允许GET请求携带request body,比如Elasticsearch的search接口。 但这不是一种好的实现。 HTTP的规范在定义GET的语义时,规定server端应该只根据请求的URI来返回response。

[…] if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request. […] The GET method means retrieve whatever information ([…]) is identified by the Request-URI. […] A payload within a GET request message has no defined semantics; sending a payload body on a GET request might cause some existing implementations to reject the request.

一个特定的http server,比如Elasticsearch,可以支持带body的GET。 但是也有很多http server会遵守规范,忽略GET的body,比如代理、cache server等。 假如在Elasticsearch前放置一个http cache server,比如Varnish,那么Varnish在丢弃GET的body后会返回一个错误的响应。

另外,一些http的客户端也可能不支持发送带body的GET请求,比如一些Javascprit的http client。 (虽然用X-HTTP-Method-Override header可以让http client以POST的方式发送GET请求来绕过这个限制, 但是如果请求流过某些代理,这些自定义的header可能会被丢弃。)

所以如果要在GET请求里携带数据,最好放在query string里,或者自定义的header(比如X-CSRF-TOKEN)、cookie里。

实际上,Elasticsearch也意识到这个问题,所以它支持通过query string来进行搜索。 (把整个body url encode之后放在source参数里,然后在source_content_type参数里指定source的内容格式是application/json。) Elasticsearch还保留着对带body的GET的支持,其中一个原因是大部分http client对URL的长度有限制。 如果你不像Elasticsearch这样需要在GET请求中发送大量数据,最好还是使用query string来发送数据。

运行时发生IncompatibleClassChangeError异常

2017 Nov 28

执行Java程序时遇到一个IncompatibleClassChangeError异常。 IncompatibleClassChangeError的javadoc是这么说的,

Thrown when an incompatible class change has occurred to some class definition. The definition of some class, on which the currently executing method depends, has since changed.

意思是Java程序在执行时发现类的定义发生了变化。

通常,如果编译时依赖的类定义和运行时依赖的类定义不一致就有可能抛出这种异常。

比如,有如下程序,

 public class ChildType extends BaseType {
 }

在编译时,BaseType是一个类,编译顺利通过。 在运行时,classpath里的BaseType如果是一个接口,就会在运行时抛出IncompatibleClassChangeError异常。

其它会抛出IncompatibleClassChangeError异常的场景有,

  • implements a class
  • 静态成员变成非静态成员
  • 非静态成员变成了静态成员

我的Java程序抛出IncompatibleClassChangeError异常的原因是运行时的classpath里同时存在不同版本Guava jar包。 在一个早期的Guava版本里,Ticker是一个接口(且这个Ticker定义更早地在运行时被加载); 在另一个较新的Guava版本里,Ticker变成了一个类,并有其它代码extends Ticker。 所以在运行到新版本的Guava代码时,就会抛出异常。

另外,升级库版本时,为了防止运行时抛出IncompatibleClassChangeError异常,最好针对新的库重新编译程序 (这样编译时就能发现错误); 而不是简单替换库的jar包。

Java的赋值操作是原子的

2017 Nov 23
  Object object = someObject;

Java的写操作是原子的(atomic),所以上面的引用赋值操作是一个原子操作。 (一个例外,对longdouble类型的写操作是非原子的;会分成两步32位的写操作。)

详见JLS:

Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.

操作是原子的,不代表操作的结果对于其它线程的可见性(visibility)。 可见性由“happens-before”来保证。

Java中的动态代理

2017 Nov 7

Java的动态代理(Dynamic Proxy)支持在运行时动态地生存代理类/对象。

Map proxy = (Map) Proxy.newProxyInstance(
  this.getClass().getClassLoader(), // 指定class loader
  new Class[] { Map.class }, // 生产的代理类所要实现的接口
  new MyInvocationHandler()); // InvocationHandler对象

生产的代理类在一个class loader里只有一个。 对代理对象的所有方法调用都会经过InvocationHandlerinvoke方法。 在invoke方法里可以做任何事情;最简单是把方法调用转发给目标对象。

public class MyInvocationHandler implements InvocationHandler {
	// 可以将目标对象(target,真正干活的对象)放在构造函数里传入
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) 
      throws Throwable {
		// @proxy 是代理对象, @method 是正在被调用的方法, 
		// @args 是方法调用的参数
    }
}

Dynamic Proxy可以用来实现Spring Data的Repository。 在Spring Data中用户只需要定义Repository接口和接口里的方法,Spring Data会帮忙生成实现类。 假设在Repository接口里有一个findByUsername方法,InvocationHandler.invoke在 接收到方法调用时可以解析方法名,然后生成相应的数据库操作代码。

Http Status Code 303

2017 Sep 12

303 See Other GET methods unchanged. Others changed to GET (body lost). Used to redirect after a PUT or a POST to prevent a refresh of the page that would re-trigger the operation.

假设之前请求的method不是GET,那么重定向302导致的请求可能会维持method不变。 (“GET methods unchanged. Others may or may not be changed to GET.”) 重定向303的不同之处是,它会把method改成GET

一个用例是,一般用户登录的请求是POST;登录失败时,需要重定向到之前的登录页面,访问这个登录页面应该要GET。 所以,重定向时要用303。

DNS里的A和CNAME records

2017 Aug 7

The A record points a name to a specific IP.

blog.dnsimple.com.     A        185.31.17.133

The CNAME record points a name to another name, instead of an IP. The CNAME source represents an alias …

blog.dnsimple.com.      CNAME   aetrion.github.io.
DNS

Chrome调试时的net::ERR_BLOCKED_BY_CLIENT错误

2017 Jun 14

在用Chrome做调试时,发现始终不能GET某个.js资源。 在“Network”里,发现对这个.js的GET请求没有显示任何status code(不是常见的404错误)。 在Console里,发现有如下的报错:

GET http://foo.com/bar.js net::ERR_BLOCKED_BY_CLIENT

最后发现是Chrome的AbBlock插件引起的问题。 因为bar.js里有一些弹框的代码,所以AbBlock为了阻止弹框广告,阻止了对它的请求和执行。 在域上禁用AbBlock后,就一切正常了。

web

Bash中的Meta键

2017 Apr 11

在Bash中输入命令时,可以使用一些快捷键。 比如输入Ctrl+a可以使光标回到行首。 Bash是用Readline库来处理输入的,实际上这些快捷键是Readline的快捷键。 在man bash的“Readline Key Bindings”章节可以看到详细的快捷键。

除了用Ctrl组合的快捷键,还有用Meta组合的快捷键。 比如Meta+f,可以使光标前进一个word。

实际上,现代的键盘并没有“Meta键”。只有一些古老的键盘上有“Meta键”。

meta key

(By Retro-Computing Society of Rhode Island - Own work, CC BY-SA 3.0, Link

现在一般用Alt键来作为“Meta键”。 比如macOS自带的“终端”程序,就是这样(“终端”->“偏好设置”->“描述文件”->“键盘”->“将Option键做为Meta键”)。

对于iTerm2(Build 3.0.15),如果要用Alt键作为“Meta键”则需要如下配置,

iTerm2->Preferences->Profiles->Keys->Left option key acts as +Esc

For most users, selecting “+Esc” here is the right choice. The “Meta” option sets the high bit of the input character, and is not compatible with modern systems.

rig,生成随机地址信息的命令行工具

2017 Apr 7

有个命令行工具叫做rig(Random Identity Generator),可以用来生成假的地址信息(美国)。

Rig is a utility that will piece together a random first name, last name, street number and address, along with a geographically consistant (ie, they all match the same area) city, state, ZIP code, and area code.

$ rig
Fred Mcdaniel
318 Plymth Terr
Plainfield, NJ  07061
(908) xxx-xxxx

OS X可以用Homebrew安装,

$ brew install rig

如果没有装命令行工具,也可以直接访问API,https://helloacm.com/api/rig/。 API有rate limit。

$ curl https://helloacm.com/api/rig/
"Ron Suarez\n538 Buncaneer Dr\nIrving, TX  75061\n(903) xxx-xxxx\n"

$ curl https://helloacm.com/api/rig/?c=2
"Kari Carson\n241 Second St\nRome, GA  30161\n(404) xxx-xxxx\n\nTheron Webster\n478 Spring County Blvd\nMiami, FL  33152\n(305) xxx-xxxx\n"
CLI

JPA 101

2017 Apr 6

If there’s one thing you have to understand to successfully use JPA (Java Persistence API) it’s the concept of a Cache. Almost everything boils down to the Cache at one point or another.

Here’s a quick cheat sheet of the JPA world:

  • A Cache is a copy of data, copy meaning pulled from but living outside the database.
  • Flushing a Cache is the act of putting modified data back into the database.
  • A PersistenceContext is essentially a Cache. It also tends to have it’s own non-shared database connection.
  • An EntityManager represents a PersistenceContext (and therefore a Cache)
  • An EntityManagerFactory creates an EntityManager (and therefore a PersistenceContext/Cache)

本质上,ORM框架关注的是数据库里的数据和它们的缓存(内存对象)之间的交互。

文章里讲的cache主要指的是“一级缓存(first level cache)”。

jpa