Skip to content

Commit 12266c0

Browse files
committed
Add more header decoding
1 parent 45b7f7a commit 12266c0

File tree

7 files changed

+169
-2
lines changed

7 files changed

+169
-2
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
# Changelog
33
Newest updates are at the top of this file.
44

5+
## 202x xxx xx - V2.0.1
6+
* MQXQH
7+
- get_header() function to extract structure (#4)
8+
- get_embedded_md() function
9+
* Added MQMDE class for if you want to parse MQXQH messages
10+
511
## 2025 Oct 16 - V2.0.0
612
* First production release, based on MQ 9.4.4
713
* Fix SETMP/INQMP problem found in beta

code/examples/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ You might need to change these assumptions in the examples for your own queue ma
3131
* async_consume
3232
* inq_set
3333
* dlq_browse
34+
* hdr_browse
3435
* channel_compression
3536
* message_priority
3637
* message_properties

code/examples/hdr_browse.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# More examples are at https://github.com/ibm-messaging/mq-dev-patterns
2+
# and in code/examples in the source distribution.
3+
4+
"""
5+
This example browses a queue, formatting any known MQ header structures that it finds. It is
6+
an extension to the DLH example, in that it deals with a number of other headers
7+
including the XQH found on messages on transmission queues.
8+
"""
9+
10+
import ibmmq as mq
11+
from ibmmq import CMQC
12+
13+
queue_manager = 'QM1'
14+
channel = 'DEV.ADMIN.SVRCONN'
15+
host = '127.0.0.1'
16+
port = '1414'
17+
conn_info = '%s(%s)' % (host, port)
18+
user = 'admin'
19+
password = 'password'
20+
21+
qmgr = mq.connect(queue_manager, channel, conn_info, user, password)
22+
23+
ok = True
24+
25+
od = mq.OD()
26+
# This is an XMITQ associated with a STOPPED channel, because we want to look at XQH processing
27+
od.ObjectName = 'QM2.STOPPED'
28+
29+
q = mq.Queue(qmgr, od, CMQC.MQOO_BROWSE)
30+
31+
gmo = mq.GMO()
32+
gmo.Options = CMQC.MQGMO_BROWSE_FIRST
33+
cnt = 1
34+
35+
while ok:
36+
try:
37+
md = mq.MD()
38+
msg = q.get(None, md, gmo)
39+
40+
print('------------------------------------')
41+
print(f"Message: {cnt}")
42+
cnt += 1
43+
44+
gmo.Options = CMQC.MQGMO_BROWSE_NEXT
45+
fmt = md['Format']
46+
47+
headers = True
48+
offset = 0
49+
ccsid = md['CodedCharSetId']
50+
51+
# Iterate through headers that we might expect to see until there are no more.
52+
while headers:
53+
print()
54+
print('------------')
55+
print(f'Header: {bytes.decode(fmt, "utf8")}')
56+
print('------------')
57+
58+
if fmt == CMQC.MQFMT_XMIT_Q_HEADER:
59+
# The XQH definition in Python has only the first part of the XQH in C, with the
60+
# embedded MQMD excluded. But We can extract both parts explicitly with XQH methods.
61+
xqh = mq.XQH().get_header(msg)
62+
print(xqh.to_string())
63+
64+
# The to_string method changes structure contents, so we first stash the
65+
# Format in its bytes version. That matches the CMQC definition as a bytes-string.
66+
emd = mq.XQH().get_embedded_md(msg)
67+
fmt = emd['Format']
68+
ccsid = emd['CodedCharSetId']
69+
# print(f'G = {emd['GroupId']}')
70+
71+
print()
72+
offset += CMQC.MQXQH_CURRENT_LENGTH
73+
74+
print(emd.to_string())
75+
76+
elif fmt == CMQC.MQFMT_MD_EXTENSION:
77+
# This will only be seen when looking at transmission queues with the XQH block
78+
mde = mq.MDE().unpack(msg[offset:offset + CMQC.MQMDE_CURRENT_LENGTH])
79+
fmt = mde['Format']
80+
ccsid = mde['CodedCharSetId']
81+
offset += CMQC.MQMDE_CURRENT_LENGTH
82+
83+
print(mde.to_string())
84+
85+
elif fmt == CMQC.MQFMT_RF_HEADER_2:
86+
rfh2 = mq.RFH2()
87+
rfh2.unpack(msg[offset:], 'utf8')
88+
fmt = rfh2['Format']
89+
offset += rfh2['StrucLength']
90+
91+
print(rfh2.to_string())
92+
93+
elif fmt == CMQC.MQFMT_DEAD_LETTER_HEADER:
94+
dlh = mq.DLH()
95+
dlh.unpack(msg[offset:offset + CMQC.MQDLH_CURRENT_LENGTH])
96+
fmt = dlh['Format']
97+
ccsid = dlh['CodedCharSetId']
98+
offset += CMQC.MQDLH_CURRENT_LENGTH
99+
100+
print(dlh.to_string())
101+
102+
else:
103+
headers = False
104+
105+
# And now print the message body. Strings get converted, otherwise just print bytes
106+
print()
107+
print('Message Body:')
108+
109+
if fmt == CMQC.MQFMT_STRING:
110+
cp = 'utf8' # Assume a codepage, though we might want to use the ccsid to be more discriminating
111+
print(bytes.decode(msg[offset:], cp))
112+
else:
113+
print(msg[offset:])
114+
115+
print()
116+
117+
except mq.MQMIError as e:
118+
if e.reason == CMQC.MQRC_NO_MSG_AVAILABLE:
119+
print('No more messages.')
120+
ok = False
121+
else:
122+
raise
123+
q.close()
124+
qmgr.disconnect()

code/ibmmq/mqadmin.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,19 @@ def __init__(self, name=None,
391391
self.__command_queue_name = command_queue_name
392392

393393
self.__convert = convert
394+
395+
# Don't allow Unlimited waits. Force it to something large, but
396+
# not forever. This gives an hour. Setting it to zero would also be foolish,
397+
# as there would be no time at all to wait for the response. But we'll allow you to
398+
# find that for yourself.
399+
if response_wait_interval < 0:
400+
response_wait_interval = 60 * 60 * 1000
394401
self.__response_wait_interval = response_wait_interval
395402

396403
if model_queue_name and reply_queue_name:
397404
raise PYIFError('Do not specify both a model_queue_name and a reply_queue_name')
398405

399-
# From here, we can treat the 2 qnames as equivalent. So assign to one and use it
406+
# From here, we can treat the 2 qnames as equivalent. So assign to one and use it.
400407
# There is an internal __reply_queue_name field, but that comes from the result of the
401408
# MQOPEN - gives the TDQ name if that's been created.
402409
if reply_queue_name:

code/ibmmq/mqopts.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,27 @@ def __getitem__(self, key: str) -> Any:
249249
"""
250250
return getattr(self, key)
251251

252+
def _remove(self, key: str) -> None:
253+
"""Remove an attribute from the object. Not meant for general use.
254+
This removes both the attribute and knowledge about how it is formatted
255+
"""
256+
# Have to convert tuples into a list so it can be modified
257+
l = list(self.__list)
258+
259+
for item in l:
260+
if item[0] == key:
261+
l.remove(item)
262+
try:
263+
delattr(self,key)
264+
except AttributeError:
265+
pass
266+
break
267+
268+
# And then convert back to the tuple format
269+
self.__list = tuple(l)
270+
271+
return
272+
252273
def __str__(self) -> str:
253274
rv = ''
254275
for i in self.__list:

code/ibmmq/mqxqh.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ def get_header(self, buf):
3636
# Return the MQMD that is part of the real (full) MQXQH structure. It is always
3737
# an MQMDv1, but this package usually deals with MQMDv2. So we extract the block and add
3838
# a zero-filled pad array to ensure it's the right length. Which means that the MDv2 fields
39-
# (eg GroupId) may not be accurate if the original message was using those MDv2 fields.
39+
# (eg GroupId) may not be accurate if the original message was using those MDv2 fields. So we
40+
# remove them from the returned MD.
4041
# If the Format in this MD indicates there is a subsequent MQMDE, which corresponds to
4142
# the MDv2 fields, then you can extract that and unpack it directly before then
4243
# continuing on to the rest of the message body.
@@ -46,4 +47,10 @@ def get_embedded_md(self, buf):
4647
pad = CMQC.MQMD_LENGTH_2 - CMQC.MQMD_LENGTH_1 # How long is the padding
4748
md_buf = buf[offset:CMQC.MQXQH_CURRENT_LENGTH] + bytearray(pad)
4849
md = MD().unpack(md_buf)
50+
# Remove the MDv2-specific fields.
51+
md._remove('GroupId')
52+
md._remove('MsgSeqNumber')
53+
md._remove('Offset')
54+
md._remove('MsgFlags')
55+
md._remove('OriginalLength')
4956
return md

tools/runAllExamples

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ examples=`ls ../code/examples/*py |\
4444
grep -v "async_consume" |\
4545
grep -v put_get_correl_id |\
4646
grep -v publish |\
47+
grep -v hdr_browse |\
4748
grep -v subscribe`
4849
for testcase in $examples
4950
do

0 commit comments

Comments
 (0)